diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..49bf21e8a2cd184bb7b7446d01a501c1cd32c522
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,379 @@
+# Created by https://www.toptal.com/developers/gitignore/api/tags,c,visualstudiocode,vim,emacs,clion,eclipse,qtcreator
+# Edit at https://www.toptal.com/developers/gitignore?templates=tags,c,visualstudiocode,vim,emacs,clion,eclipse,qtcreator
+
+### C ###
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+### CLion ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### CLion Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Eclipse Patch ###
+# Spring Boot Tooling
+.sts4-cache/
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
+### QtCreator ###
+# gitignore for Qt Creator like IDE for pure C/C++ project without Qt
+#
+# Reference: http://doc.qt.io/qtcreator/creator-project-generic.html
+
+
+
+# Qt Creator autogenerated files
+
+
+# A listing of all the files included in the project
+*.files
+
+# Include directories
+*.includes
+
+# Project configuration settings like predefined Macros
+*.config
+
+# Qt Creator settings
+*.creator
+
+# User project settings
+*.creator.user*
+
+# Qt Creator backups
+*.autosave
+
+# Flags for Clang Code Model
+*.cxxflags
+*.cflags
+
+
+### Tags ###
+# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
+TAGS
+.TAGS
+!TAGS/
+tags
+.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+GSYMS
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
+
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+!*.svg  # comment out if you don't need vector files
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+Sessionx.vim
+
+# Temporary
+.netrwhist
+# Auto-generated tag files
+# Persistent undo
+[._]*.un~
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+# End of https://www.toptal.com/developers/gitignore/api/tags,c,visualstudiocode,vim,emacs,clion,eclipse,qtcreator
+
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..45e9885c61f5bf18fb29212334600ef797b94b14
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+    "C_Cpp.errorSquiggles": "disabled",
+    "files.associations": {
+        "list.h": "c",
+        "syscall.h": "c"
+    }
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..8702541a9c913e71669cc6ef62cef16543f688c4
--- /dev/null
+++ b/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/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..610c91125a7df9b2135bbf3f79df3b9e7764c179
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+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
+	rm -f cscope.files cscope.in.out cscope.out cscope.po.out
+
+distclean:: clean
+	find . -name '*~' -exec rm '{}' \;
+
+TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib
+TAGS_SOURCES = find $(TAGS_SUBDIRS) -name \*.[chS] -print
+
+TAGS::
+	etags --members `$(TAGS_SOURCES)`
+
+tags::
+	ctags --fields=+l `$(TAGS_SOURCES)`
+
+cscope.files::
+	$(TAGS_SOURCES) > cscope.files
+
+cscope:: cscope.files
+	cscope -b -q -k
diff --git a/Makefile.build b/Makefile.build
new file mode 100644
index 0000000000000000000000000000000000000000..d6bd889d3fbe2305ccd53d2c97aa451d095cc222
--- /dev/null
+++ b/Makefile.build
@@ -0,0 +1,113 @@
+# -*- makefile -*-
+
+SRCDIR = ../..
+
+all: kernel.bin loader.bin
+
+include ../../Make.config
+include ../Make.vars
+include ../../tests/Make.tests
+
+# Compiler and assembler options.
+kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel
+
+# Core kernel.
+threads_SRC  = threads/start.S		# Startup code.
+threads_SRC += threads/init.c		# Main program.
+threads_SRC += threads/thread.c		# Thread management core.
+threads_SRC += threads/switch.S		# Thread switch routine.
+threads_SRC += threads/interrupt.c	# Interrupt core.
+threads_SRC += threads/intr-stubs.S	# Interrupt stubs.
+threads_SRC += threads/synch.c		# Synchronization.
+threads_SRC += threads/palloc.c		# Page allocator.
+threads_SRC += threads/malloc.c		# Subpage allocator.
+
+# Device driver code.
+devices_SRC  = devices/pit.c		# Programmable interrupt timer chip.
+devices_SRC += devices/timer.c		# Periodic timer device.
+devices_SRC += devices/kbd.c		# Keyboard device.
+devices_SRC += devices/vga.c		# Video device.
+devices_SRC += devices/serial.c		# Serial port device.
+devices_SRC += devices/block.c		# Block device abstraction layer.
+devices_SRC += devices/partition.c	# Partition block device.
+devices_SRC += devices/ide.c		# IDE disk block device.
+devices_SRC += devices/input.c		# Serial and keyboard input.
+devices_SRC += devices/intq.c		# Interrupt queue.
+devices_SRC += devices/rtc.c		# Real-time clock.
+devices_SRC += devices/shutdown.c	# Reboot and power off.
+devices_SRC += devices/speaker.c	# PC speaker.
+
+# Library code shared between kernel and user programs.
+lib_SRC  = lib/debug.c			# Debug helpers.
+lib_SRC += lib/random.c			# Pseudo-random numbers.
+lib_SRC += lib/stdio.c			# I/O library.
+lib_SRC += lib/stdlib.c			# Utility functions.
+lib_SRC += lib/string.c			# String functions.
+lib_SRC += lib/arithmetic.c		# 64-bit arithmetic for GCC.
+lib_SRC += lib/ustar.c			# Unix standard tar format utilities.
+
+# Kernel-specific library code.
+lib/kernel_SRC  = lib/kernel/debug.c	# Debug helpers.
+lib/kernel_SRC += lib/kernel/list.c		# Doubly-linked lists.
+lib/kernel_SRC += lib/kernel/bitmap.c	# Bitmaps.
+lib/kernel_SRC += lib/kernel/hash.c		# Hash tables.
+lib/kernel_SRC += lib/kernel/console.c	# printf(), putchar().
+
+# User process code.
+userprog_SRC  = userprog/process.c		# Process loading.
+userprog_SRC += userprog/pagedir.c		# Page directories.
+userprog_SRC += userprog/exception.c	# User exception handler.
+userprog_SRC += userprog/syscall.c		# System call handler.
+userprog_SRC += userprog/gdt.c			# GDT initialization.
+userprog_SRC += userprog/tss.c			# TSS management.
+userprog_SRC += userprog/slowdown.c		# Slowdown of syscalls for debugging.
+
+# No virtual memory code yet.
+#vm_SRC = vm/file.c			# Some file.
+
+# Filesystem code.
+filesys_SRC  = filesys/filesys.c	# Filesystem core.
+filesys_SRC += filesys/free-map.c	# Free sector bitmap.
+filesys_SRC += filesys/file.c		# Files.
+filesys_SRC += filesys/directory.c	# Directories.
+filesys_SRC += filesys/inode.c		# File headers.
+filesys_SRC += filesys/fsutil.c		# Utilities.
+
+SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC))
+OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES)))
+DEPENDS = $(patsubst %.o,%.d,$(OBJECTS))
+
+threads/kernel.lds.s: CPPFLAGS += -P
+threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h
+
+kernel.o: threads/kernel.lds.s $(OBJECTS) 
+	$(LD) -T $< -o $@ $(OBJECTS)
+
+kernel.bin: kernel.o
+	$(OBJCOPY) -R .note -R .comment -S $< $@
+
+threads/loader.o: threads/loader.S
+	$(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES)
+
+loader.bin: threads/loader.o
+	#$(LD) -N -e start -Ttext 0x7c00 --oformat binary -o $@ $<
+	$(LD) -N -e start -Ttext 0x7c00 -o $@.tmp $<
+	$(OBJCOPY) -O binary -R .note -R .note.* -R .comment -S $@.tmp $@
+	rm $@.tmp
+
+os.dsk: kernel.bin
+	cat $^ > $@
+
+clean::
+	rm -f $(OBJECTS) $(DEPENDS) 
+	rm -f threads/loader.o threads/kernel.lds.s threads/loader.d
+	rm -f kernel.bin.tmp
+	rm -f kernel.o kernel.lds.s
+	rm -f kernel.bin loader.bin
+	rm -f bochsout.txt bochsrc.txt
+	rm -f results grade
+
+Makefile: $(SRCDIR)/Makefile.build
+	cp $< $@
+
+-include $(DEPENDS)
diff --git a/Makefile.kernel b/Makefile.kernel
new file mode 100644
index 0000000000000000000000000000000000000000..162a4114256891794606019b106dc6d67353c323
--- /dev/null
+++ b/Makefile.kernel
@@ -0,0 +1,20 @@
+# -*- makefile -*-
+
+all:
+
+include Make.vars
+
+DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user))
+
+all grade check: $(DIRS) build/Makefile
+	cd build && $(MAKE) $@
+$(DIRS):
+	mkdir -p $@
+build/Makefile: ../Makefile.build
+	cp $< $@
+
+build/%: $(DIRS) build/Makefile
+	cd build && $(MAKE) $*
+
+clean:
+	rm -rf build
diff --git a/Makefile.userprog b/Makefile.userprog
new file mode 100644
index 0000000000000000000000000000000000000000..0df391a24ed164db4fc6febe3d44c33443a30769
--- /dev/null
+++ b/Makefile.userprog
@@ -0,0 +1,52 @@
+# -*- makefile -*-
+
+$(PROGS): CPPFLAGS += -I$(SRCDIR)/lib/user -I.
+
+# Linker flags.
+$(PROGS): LDFLAGS += -nostdlib -static -Wl,-T,$(LDSCRIPT)
+$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/user.lds
+
+# Library code shared between kernel and user programs.
+lib_SRC  = lib/debug.c			# Debug code.
+lib_SRC += lib/random.c			# Pseudo-random numbers.
+lib_SRC += lib/stdio.c			# I/O library.
+lib_SRC += lib/stdlib.c			# Utility functions.
+lib_SRC += lib/string.c			# String functions.
+lib_SRC += lib/arithmetic.c		# 64-bit arithmetic for GCC.
+lib_SRC += lib/ustar.c			# Unix standard tar format utilities.
+
+# User level only library code.
+lib/user_SRC  = lib/user/debug.c	# Debug helpers.
+lib/user_SRC += lib/user/syscall.c	# System calls.
+lib/user_SRC += lib/user/console.c	# Console code.
+
+LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(lib_SRC) $(lib/user_SRC)))
+LIB_DEP = $(patsubst %.o,%.d,$(LIB_OBJ))
+LIB = lib/user/entry.o libc.a
+
+PROGS_SRC = $(foreach prog,$(PROGS),$($(prog)_SRC))
+PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC)))
+PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ))
+
+all: $(PROGS)
+
+define TEMPLATE
+$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC)))
+$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT)
+	$$(CC) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) -o $$@
+endef
+
+$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog))))
+
+libc.a: $(LIB_OBJ)
+	rm -f $@
+	ar r $@ $^
+	ranlib $@
+
+clean::
+	rm -f $(PROGS) $(PROGS_OBJ) $(PROGS_DEP)
+	rm -f $(LIB_DEP) $(LIB_OBJ) lib/user/entry.[do] libc.a 
+
+.PHONY: all clean
+
+-include $(LIB_DEP) $(PROGS_DEP)
diff --git a/devices/block.c b/devices/block.c
new file mode 100644
index 0000000000000000000000000000000000000000..5f945c56ddb08d8d78f0f3522676271333e7d321
--- /dev/null
+++ b/devices/block.c
@@ -0,0 +1,215 @@
+#include "devices/block.h"
+
+#include "devices/ide.h"
+#include "threads/malloc.h"
+
+#include <list.h>
+#include <stdio.h>
+#include <string.h>
+
+/* A block device. */
+struct block {
+	struct list_elem list_elem; /* Element in all_blocks. */
+
+	char name[16];			 /* Block device name. */
+	enum block_type type; /* Type of block device. */
+	block_sector_t size;	 /* Size in sectors. */
+
+	const struct block_operations* ops; /* Driver operations. */
+	void* aux;									/* Extra data owned by driver. */
+
+	unsigned long long read_cnt;	/* Number of sectors read. */
+	unsigned long long write_cnt; /* Number of sectors written. */
+};
+
+/* List of all block devices. */
+static struct list all_blocks = LIST_INITIALIZER(all_blocks);
+
+/* The block block assigned to each Pintos role. */
+static struct block* block_by_role[BLOCK_ROLE_CNT];
+
+static struct block* list_elem_to_block(struct list_elem*);
+
+/* Returns a human-readable name for the given block device
+	TYPE. */
+const char* block_type_name(enum block_type type)
+{
+	static const char* block_type_names[BLOCK_CNT] = {
+		 "kernel",
+		 "filesys",
+		 "scratch",
+		 "swap",
+		 "raw",
+		 "foreign",
+	};
+
+	ASSERT(type < BLOCK_CNT);
+	return block_type_names[type];
+}
+
+/* Returns the block device fulfilling the given ROLE, or a null
+	pointer if no block device has been assigned that role. */
+struct block* block_get_role(enum block_type role)
+{
+	ASSERT(role < BLOCK_ROLE_CNT);
+	return block_by_role[role];
+}
+
+/* Assigns BLOCK the given ROLE. */
+void block_set_role(enum block_type role, struct block* block)
+{
+	ASSERT(role < BLOCK_ROLE_CNT);
+	block_by_role[role] = block;
+}
+
+/* Returns the first block device in kernel probe order, or a
+	null pointer if no block devices are registered. */
+struct block* block_first(void)
+{
+	return list_elem_to_block(list_begin(&all_blocks));
+}
+
+/* Returns the block device following BLOCK in kernel probe
+	order, or a null pointer if BLOCK is the last block device. */
+struct block* block_next(struct block* block)
+{
+	return list_elem_to_block(list_next(&block->list_elem));
+}
+
+/* Returns the block device with the given NAME, or a null
+	pointer if no block device has that name. */
+struct block* block_get_by_name(const char* name)
+{
+	struct list_elem* e;
+
+	for (e = list_begin(&all_blocks); e != list_end(&all_blocks); e = list_next(e)) {
+		struct block* block = list_entry(e, struct block, list_elem);
+		if (!strcmp(name, block->name))
+			return block;
+	}
+
+	return NULL;
+}
+
+/* Verifies that SECTOR is a valid offset within BLOCK.
+	Panics if not. */
+static void check_sector(struct block* block, block_sector_t sector)
+{
+	if (sector >= block->size) {
+		/* We do not use ASSERT because we want to panic here
+			regardless of whether NDEBUG is defined. */
+		PANIC(
+			 "Access past end of device %s (sector=%" PRDSNu
+			 ", "
+			 "size=%" PRDSNu ")\n",
+			 block_name(block),
+			 sector,
+			 block->size);
+	}
+}
+
+/* Reads sector SECTOR from BLOCK into BUFFER, which must
+	have room for BLOCK_SECTOR_SIZE bytes.
+	Internally synchronizes accesses to block devices, so external
+	per-block device locking is unneeded. */
+void block_read(struct block* block, block_sector_t sector, void* buffer)
+{
+	check_sector(block, sector);
+	block->ops->read(block->aux, sector, buffer);
+	block->read_cnt++;
+}
+
+/* Write sector SECTOR to BLOCK from BUFFER, which must contain
+	BLOCK_SECTOR_SIZE bytes.  Returns after the block device has
+	acknowledged receiving the data.
+	Internally synchronizes accesses to block devices, so external
+	per-block device locking is unneeded. */
+void block_write(struct block* block, block_sector_t sector, const void* buffer)
+{
+	check_sector(block, sector);
+	ASSERT(block->type != BLOCK_FOREIGN);
+	block->ops->write(block->aux, sector, buffer);
+	block->write_cnt++;
+}
+
+/* Returns the number of sectors in BLOCK. */
+block_sector_t block_size(struct block* block)
+{
+	return block->size;
+}
+
+/* Returns BLOCK's name (e.g. "hda"). */
+const char* block_name(struct block* block)
+{
+	return block->name;
+}
+
+/* Returns BLOCK's type. */
+enum block_type block_type(struct block* block)
+{
+	return block->type;
+}
+
+/* Prints statistics for each block device used for a Pintos role. */
+void block_print_stats(void)
+{
+	int i;
+
+	for (i = 0; i < BLOCK_ROLE_CNT; i++) {
+		struct block* block = block_by_role[i];
+		if (block != NULL) {
+			printf(
+				 "%s (%s): %llu reads, %llu writes\n",
+				 block->name,
+				 block_type_name(block->type),
+				 block->read_cnt,
+				 block->write_cnt);
+		}
+	}
+}
+
+/* Registers a new block device with the given NAME.  If
+	EXTRA_INFO is non-null, it is printed as part of a user
+	message.  The block device's SIZE in sectors and its TYPE must
+	be provided, as well as the it operation functions OPS, which
+	will be passed AUX in each function call. */
+struct block* block_register(
+	 const char* name,
+	 enum block_type type,
+	 const char* extra_info,
+	 block_sector_t size,
+	 const struct block_operations* ops,
+	 void* aux)
+{
+	struct block* block = malloc(sizeof *block);
+	if (block == NULL)
+		PANIC("Failed to allocate memory for block device descriptor");
+
+	list_push_back(&all_blocks, &block->list_elem);
+	strlcpy(block->name, name, sizeof block->name);
+	block->type = type;
+	block->size = size;
+	block->ops = ops;
+	block->aux = aux;
+	block->read_cnt = 0;
+	block->write_cnt = 0;
+
+	printf("%s: %'" PRDSNu " sectors (", block->name, block->size);
+	print_human_readable_size((uint64_t) block->size * BLOCK_SECTOR_SIZE);
+	printf(")");
+	if (extra_info != NULL)
+		printf(", %s", extra_info);
+	printf("\n");
+
+	return block;
+}
+
+/* Returns the block device corresponding to LIST_ELEM, or a null
+	pointer if LIST_ELEM is the list end of all_blocks. */
+static struct block* list_elem_to_block(struct list_elem* list_elem)
+{
+	return (
+		 list_elem != list_end(&all_blocks)
+			  ? list_entry(list_elem, struct block, list_elem)
+			  : NULL);
+}
diff --git a/devices/block.h b/devices/block.h
new file mode 100644
index 0000000000000000000000000000000000000000..25877629deebbba4e115b67c8a31298ee1cbb330
--- /dev/null
+++ b/devices/block.h
@@ -0,0 +1,76 @@
+#ifndef DEVICES_BLOCK_H
+#define DEVICES_BLOCK_H
+
+#include <inttypes.h>
+#include <stddef.h>
+
+/* Size of a block device sector in bytes.
+	All IDE disks use this sector size, as do most USB and SCSI
+	disks.  It's not worth it to try to cater to other sector
+	sizes in Pintos (yet). */
+#define BLOCK_SECTOR_SIZE 512
+
+/* Index of a block device sector.
+	Good enough for devices up to 2 TB. */
+typedef uint32_t block_sector_t;
+
+/* Format specifier for printf(), e.g.:
+	printf ("sector=%"PRDSNu"\n", sector); */
+#define PRDSNu PRIu32
+
+/* Higher-level interface for file systems, etc. */
+
+struct block;
+
+/* Type of a block device. */
+enum block_type {
+	/* Block device types that play a role in Pintos. */
+	BLOCK_KERNEL,	/* Pintos OS kernel. */
+	BLOCK_FILESYS, /* File system. */
+	BLOCK_SCRATCH, /* Scratch. */
+	BLOCK_SWAP,		/* Swap. */
+	BLOCK_ROLE_CNT,
+
+	/* Other kinds of block devices that Pintos may see but does
+		not interact with. */
+	BLOCK_RAW = BLOCK_ROLE_CNT, /* "Raw" device with unidentified contents. */
+	BLOCK_FOREIGN,					 /* Owned by non-Pintos operating system. */
+	BLOCK_CNT						 /* Number of Pintos block types. */
+};
+
+const char* block_type_name(enum block_type);
+
+/* Finding block devices. */
+struct block* block_get_role(enum block_type);
+void block_set_role(enum block_type, struct block*);
+struct block* block_get_by_name(const char* name);
+
+struct block* block_first(void);
+struct block* block_next(struct block*);
+
+/* Block device operations. */
+block_sector_t block_size(struct block*);
+void block_read(struct block*, block_sector_t, void*);
+void block_write(struct block*, block_sector_t, const void*);
+const char* block_name(struct block*);
+enum block_type block_type(struct block*);
+
+/* Statistics. */
+void block_print_stats(void);
+
+/* Lower-level interface to block device drivers. */
+
+struct block_operations {
+	void (*read)(void* aux, block_sector_t, void* buffer);
+	void (*write)(void* aux, block_sector_t, const void* buffer);
+};
+
+struct block* block_register(
+	 const char* name,
+	 enum block_type,
+	 const char* extra_info,
+	 block_sector_t size,
+	 const struct block_operations*,
+	 void* aux);
+
+#endif /* devices/block.h */
diff --git a/devices/ide.c b/devices/ide.c
new file mode 100644
index 0000000000000000000000000000000000000000..aa7a5d1490064791bc3e564971095856388602aa
--- /dev/null
+++ b/devices/ide.c
@@ -0,0 +1,484 @@
+#include "devices/ide.h"
+
+#include "devices/block.h"
+#include "devices/partition.h"
+#include "devices/timer.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+#include "threads/synch.h"
+
+#include <ctype.h>
+#include <debug.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+/* The code in this file is an interface to an ATA (IDE)
+	controller.  It attempts to comply to [ATA-3]. */
+
+/* ATA command block port addresses. */
+#define reg_data(CHANNEL)	  ((CHANNEL)->reg_base + 0) /* Data. */
+#define reg_error(CHANNEL)	  ((CHANNEL)->reg_base + 1) /* Error. */
+#define reg_nsect(CHANNEL)	  ((CHANNEL)->reg_base + 2) /* Sector Count. */
+#define reg_lbal(CHANNEL)	  ((CHANNEL)->reg_base + 3) /* LBA 0:7. */
+#define reg_lbam(CHANNEL)	  ((CHANNEL)->reg_base + 4) /* LBA 15:8. */
+#define reg_lbah(CHANNEL)	  ((CHANNEL)->reg_base + 5) /* LBA 23:16. */
+#define reg_device(CHANNEL)  ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */
+#define reg_status(CHANNEL)  ((CHANNEL)->reg_base + 7) /* Status (r/o). */
+#define reg_command(CHANNEL) reg_status(CHANNEL)		 /* Command (w/o). */
+
+/* ATA control block port addresses.
+	(If we supported non-legacy ATA controllers this would not be
+	flexible enough, but it's fine for what we do.) */
+#define reg_ctl(CHANNEL)		  ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */
+#define reg_alt_status(CHANNEL) reg_ctl(CHANNEL)				  /* Alt Status (r/o). */
+
+/* Alternate Status Register bits. */
+#define STA_BSY  0x80 /* Busy. */
+#define STA_DRDY 0x40 /* Device Ready. */
+#define STA_DRQ  0x08 /* Data Request. */
+
+/* Control Register bits. */
+#define CTL_SRST 0x04 /* Software Reset. */
+
+/* Device Register bits. */
+#define DEV_MBS 0xa0 /* Must be set. */
+#define DEV_LBA 0x40 /* Linear based addressing. */
+#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */
+
+/* Commands.
+	Many more are defined but this is the small subset that we
+	use. */
+#define CMD_IDENTIFY_DEVICE	 0xec /* IDENTIFY DEVICE. */
+#define CMD_READ_SECTOR_RETRY	 0x20 /* READ SECTOR with retries. */
+#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */
+
+/* An ATA device. */
+struct ata_disk {
+	char name[8];				 /* Name, e.g. "hda". */
+	struct channel* channel; /* Channel that disk is attached to. */
+	int dev_no;					 /* Device 0 or 1 for master or slave. */
+	bool is_ata;				 /* Is device an ATA disk? */
+};
+
+/* An ATA channel (aka controller).
+	Each channel can control up to two disks. */
+struct channel {
+	char name[8];		 /* Name, e.g. "ide0". */
+	uint16_t reg_base; /* Base I/O port. */
+	uint8_t irq;		 /* Interrupt in use. */
+
+	struct lock lock;						 /* Must acquire to access the controller. */
+	bool expecting_interrupt;			 /* True if an interrupt is expected, false if
+													 any interrupt would be spurious. */
+	struct semaphore completion_wait; /* Up'd by interrupt handler. */
+
+	struct ata_disk devices[2]; /* The devices on this channel. */
+};
+
+/* We support the two "legacy" ATA channels found in a standard PC. */
+#define CHANNEL_CNT 2
+static struct channel channels[CHANNEL_CNT];
+
+static struct block_operations ide_operations;
+
+static void reset_channel(struct channel*);
+static bool check_device_type(struct ata_disk*);
+static void identify_ata_device(struct ata_disk*);
+
+static void select_sector(struct ata_disk*, block_sector_t);
+static void issue_pio_command(struct channel*, uint8_t command);
+static void input_sector(struct channel*, void*);
+static void output_sector(struct channel*, const void*);
+
+static void wait_until_idle(const struct ata_disk*);
+static bool wait_while_busy(const struct ata_disk*);
+static void select_device(const struct ata_disk*);
+static void select_device_wait(const struct ata_disk*);
+
+static void interrupt_handler(struct intr_frame*);
+
+/* Initialize the disk subsystem and detect disks. */
+void ide_init(void)
+{
+	size_t chan_no;
+
+	for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) {
+		struct channel* c = &channels[chan_no];
+		int dev_no;
+
+		/* Initialize channel. */
+		snprintf(c->name, sizeof c->name, "ide%zu", chan_no);
+		switch (chan_no) {
+			case 0:
+				c->reg_base = 0x1f0;
+				c->irq = 14 + 0x20;
+				break;
+			case 1:
+				c->reg_base = 0x170;
+				c->irq = 15 + 0x20;
+				break;
+			default:
+				NOT_REACHED();
+		}
+		lock_init(&c->lock);
+		c->expecting_interrupt = false;
+		sema_init(&c->completion_wait, 0);
+
+		/* Initialize devices. */
+		for (dev_no = 0; dev_no < 2; dev_no++) {
+			struct ata_disk* d = &c->devices[dev_no];
+			snprintf(d->name, sizeof d->name, "hd%c", 'a' + chan_no * 2 + dev_no);
+			d->channel = c;
+			d->dev_no = dev_no;
+			d->is_ata = false;
+		}
+
+		/* Register interrupt handler. */
+		intr_register_ext(c->irq, interrupt_handler, c->name);
+
+		/* Reset hardware. */
+		reset_channel(c);
+
+		/* Distinguish ATA hard disks from other devices. */
+		if (check_device_type(&c->devices[0]))
+			check_device_type(&c->devices[1]);
+
+		/* Read hard disk identity information. */
+		for (dev_no = 0; dev_no < 2; dev_no++)
+			if (c->devices[dev_no].is_ata)
+				identify_ata_device(&c->devices[dev_no]);
+	}
+}
+
+/* Disk detection and identification. */
+
+static char* descramble_ata_string(char*, int size);
+
+/* Resets an ATA channel and waits for any devices present on it
+	to finish the reset. */
+static void reset_channel(struct channel* c)
+{
+	bool present[2];
+	int dev_no;
+
+	/* The ATA reset sequence depends on which devices are present,
+		so we start by detecting device presence. */
+	for (dev_no = 0; dev_no < 2; dev_no++) {
+		struct ata_disk* d = &c->devices[dev_no];
+
+		select_device(d);
+
+		outb(reg_nsect(c), 0x55);
+		outb(reg_lbal(c), 0xaa);
+
+		outb(reg_nsect(c), 0xaa);
+		outb(reg_lbal(c), 0x55);
+
+		outb(reg_nsect(c), 0x55);
+		outb(reg_lbal(c), 0xaa);
+
+		present[dev_no] = (inb(reg_nsect(c)) == 0x55 && inb(reg_lbal(c)) == 0xaa);
+	}
+
+	/* Issue soft reset sequence, which selects device 0 as a side effect.
+		Also enable interrupts. */
+	outb(reg_ctl(c), 0);
+	timer_usleep(10);
+	outb(reg_ctl(c), CTL_SRST);
+	timer_usleep(10);
+	outb(reg_ctl(c), 0);
+
+	timer_msleep(150);
+
+	/* Wait for device 0 to clear BSY. */
+	if (present[0]) {
+		select_device(&c->devices[0]);
+		wait_while_busy(&c->devices[0]);
+	}
+
+	/* Wait for device 1 to clear BSY. */
+	if (present[1]) {
+		int i;
+
+		select_device(&c->devices[1]);
+		for (i = 0; i < 3000; i++) {
+			if (inb(reg_nsect(c)) == 1 && inb(reg_lbal(c)) == 1)
+				break;
+			timer_msleep(10);
+		}
+		wait_while_busy(&c->devices[1]);
+	}
+}
+
+/* Checks whether device D is an ATA disk and sets D's is_ata
+	member appropriately.  If D is device 0 (master), returns true
+	if it's possible that a slave (device 1) exists on this
+	channel.  If D is device 1 (slave), the return value is not
+	meaningful. */
+static bool check_device_type(struct ata_disk* d)
+{
+	struct channel* c = d->channel;
+	uint8_t error, lbam, lbah, status;
+
+	select_device(d);
+
+	error = inb(reg_error(c));
+	lbam = inb(reg_lbam(c));
+	lbah = inb(reg_lbah(c));
+	status = inb(reg_status(c));
+
+	if ((error != 1 && (error != 0x81 || d->dev_no == 1)) || (status & STA_DRDY) == 0
+		 || (status & STA_BSY) != 0) {
+		d->is_ata = false;
+		return error != 0x81;
+	}
+	else {
+		d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3);
+		return true;
+	}
+}
+
+/* Sends an IDENTIFY DEVICE command to disk D and reads the
+	response.  Registers the disk with the block device
+	layer. */
+static void identify_ata_device(struct ata_disk* d)
+{
+	struct channel* c = d->channel;
+	char id[BLOCK_SECTOR_SIZE];
+	block_sector_t capacity;
+	char *model, *serial;
+	char extra_info[128];
+	struct block* block;
+
+	ASSERT(d->is_ata);
+
+	/* Send the IDENTIFY DEVICE command, wait for an interrupt
+		indicating the device's response is ready, and read the data
+		into our buffer. */
+	select_device_wait(d);
+	issue_pio_command(c, CMD_IDENTIFY_DEVICE);
+	sema_down(&c->completion_wait);
+	if (!wait_while_busy(d)) {
+		d->is_ata = false;
+		return;
+	}
+	input_sector(c, id);
+
+	/* Calculate capacity.
+		Read model name and serial number. */
+	capacity = *(uint32_t*) &id[60 * 2];
+	model = descramble_ata_string(&id[10 * 2], 20);
+	serial = descramble_ata_string(&id[27 * 2], 40);
+	snprintf(
+		 extra_info, sizeof extra_info, "model \"%s\", serial \"%s\"", model, serial);
+
+	/* Disable access to IDE disks over 1 GB, which are likely
+		physical IDE disks rather than virtual ones.  If we don't
+		allow access to those, we're less likely to scribble on
+		someone's important data.  You can disable this check by
+		hand if you really want to do so. */
+	if (capacity >= 1024 * 1024 * 1024 / BLOCK_SECTOR_SIZE) {
+		printf("%s: ignoring ", d->name);
+		print_human_readable_size(capacity * 512);
+		printf("disk for safety\n");
+		d->is_ata = false;
+		return;
+	}
+
+	/* Register. */
+	block = block_register(d->name, BLOCK_RAW, extra_info, capacity, &ide_operations, d);
+	partition_scan(block);
+}
+
+/* Translates STRING, which consists of SIZE bytes in a funky
+	format, into a null-terminated string in-place.  Drops
+	trailing whitespace and null bytes.  Returns STRING.  */
+static char* descramble_ata_string(char* string, int size)
+{
+	int i;
+
+	/* Swap all pairs of bytes. */
+	for (i = 0; i + 1 < size; i += 2) {
+		char tmp = string[i];
+		string[i] = string[i + 1];
+		string[i + 1] = tmp;
+	}
+
+	/* Find the last non-white, non-null character. */
+	for (size--; size > 0; size--) {
+		int c = string[size - 1];
+		if (c != '\0' && !isspace(c))
+			break;
+	}
+	string[size] = '\0';
+
+	return string;
+}
+
+/* Reads sector SEC_NO from disk D into BUFFER, which must have
+	room for BLOCK_SECTOR_SIZE bytes.
+	Internally synchronizes accesses to disks, so external
+	per-disk locking is unneeded. */
+static void ide_read(void* d_, block_sector_t sec_no, void* buffer)
+{
+	struct ata_disk* d = d_;
+	struct channel* c = d->channel;
+	lock_acquire(&c->lock);
+	select_sector(d, sec_no);
+	issue_pio_command(c, CMD_READ_SECTOR_RETRY);
+	sema_down(&c->completion_wait);
+	if (!wait_while_busy(d))
+		PANIC("%s: disk read failed, sector=%" PRDSNu, d->name, sec_no);
+	input_sector(c, buffer);
+	lock_release(&c->lock);
+}
+
+/* Write sector SEC_NO to disk D from BUFFER, which must contain
+	BLOCK_SECTOR_SIZE bytes.  Returns after the disk has
+	acknowledged receiving the data.
+	Internally synchronizes accesses to disks, so external
+	per-disk locking is unneeded. */
+static void ide_write(void* d_, block_sector_t sec_no, const void* buffer)
+{
+	struct ata_disk* d = d_;
+	struct channel* c = d->channel;
+	lock_acquire(&c->lock);
+	select_sector(d, sec_no);
+	issue_pio_command(c, CMD_WRITE_SECTOR_RETRY);
+	if (!wait_while_busy(d))
+		PANIC("%s: disk write failed, sector=%" PRDSNu, d->name, sec_no);
+	output_sector(c, buffer);
+	sema_down(&c->completion_wait);
+	lock_release(&c->lock);
+}
+
+static struct block_operations ide_operations = {ide_read, ide_write};
+
+/* Selects device D, waiting for it to become ready, and then
+	writes SEC_NO to the disk's sector selection registers.  (We
+	use LBA mode.) */
+static void select_sector(struct ata_disk* d, block_sector_t sec_no)
+{
+	struct channel* c = d->channel;
+
+	ASSERT(sec_no < (1UL << 28));
+
+	select_device_wait(d);
+	outb(reg_nsect(c), 1);
+	outb(reg_lbal(c), sec_no);
+	outb(reg_lbam(c), sec_no >> 8);
+	outb(reg_lbah(c), (sec_no >> 16));
+	outb(
+		 reg_device(c),
+		 DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24));
+}
+
+/* Writes COMMAND to channel C and prepares for receiving a
+	completion interrupt. */
+static void issue_pio_command(struct channel* c, uint8_t command)
+{
+	/* Interrupts must be enabled or our semaphore will never be
+		up'd by the completion handler. */
+	ASSERT(intr_get_level() == INTR_ON);
+
+	c->expecting_interrupt = true;
+	outb(reg_command(c), command);
+}
+
+/* Reads a sector from channel C's data register in PIO mode into
+	SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */
+static void input_sector(struct channel* c, void* sector)
+{
+	insw(reg_data(c), sector, BLOCK_SECTOR_SIZE / 2);
+}
+
+/* Writes SECTOR to channel C's data register in PIO mode.
+	SECTOR must contain BLOCK_SECTOR_SIZE bytes. */
+static void output_sector(struct channel* c, const void* sector)
+{
+	outsw(reg_data(c), sector, BLOCK_SECTOR_SIZE / 2);
+}
+
+/* Low-level ATA primitives. */
+
+/* Wait up to 10 seconds for the controller to become idle, that
+	is, for the BSY and DRQ bits to clear in the status register.
+
+	As a side effect, reading the status register clears any
+	pending interrupt. */
+static void wait_until_idle(const struct ata_disk* d)
+{
+	int i;
+
+	for (i = 0; i < 1000; i++) {
+		if ((inb(reg_status(d->channel)) & (STA_BSY | STA_DRQ)) == 0)
+			return;
+		timer_usleep(10);
+	}
+
+	printf("%s: idle timeout\n", d->name);
+}
+
+/* Wait up to 30 seconds for disk D to clear BSY,
+	and then return the status of the DRQ bit.
+	The ATA standards say that a disk may take as long as that to
+	complete its reset. */
+static bool wait_while_busy(const struct ata_disk* d)
+{
+	struct channel* c = d->channel;
+	int i;
+
+	for (i = 0; i < 3000; i++) {
+		if (i == 700)
+			printf("%s: busy, waiting...", d->name);
+		if (!(inb(reg_alt_status(c)) & STA_BSY)) {
+			if (i >= 700)
+				printf("ok\n");
+			return (inb(reg_alt_status(c)) & STA_DRQ) != 0;
+		}
+		timer_msleep(10);
+	}
+
+	printf("failed\n");
+	return false;
+}
+
+/* Program D's channel so that D is now the selected disk. */
+static void select_device(const struct ata_disk* d)
+{
+	struct channel* c = d->channel;
+	uint8_t dev = DEV_MBS;
+	if (d->dev_no == 1)
+		dev |= DEV_DEV;
+	outb(reg_device(c), dev);
+	inb(reg_alt_status(c));
+	timer_nsleep(400);
+}
+
+/* Select disk D in its channel, as select_device(), but wait for
+	the channel to become idle before and after. */
+static void select_device_wait(const struct ata_disk* d)
+{
+	wait_until_idle(d);
+	select_device(d);
+	wait_until_idle(d);
+}
+
+/* ATA interrupt handler. */
+static void interrupt_handler(struct intr_frame* f)
+{
+	struct channel* c;
+
+	for (c = channels; c < channels + CHANNEL_CNT; c++)
+		if (f->vec_no == c->irq) {
+			if (c->expecting_interrupt) {
+				inb(reg_status(c));				/* Acknowledge interrupt. */
+				sema_up(&c->completion_wait); /* Wake up waiter. */
+			}
+			else
+				printf("%s: unexpected interrupt\n", c->name);
+			return;
+		}
+
+	NOT_REACHED();
+}
diff --git a/devices/ide.h b/devices/ide.h
new file mode 100644
index 0000000000000000000000000000000000000000..0823e890c71bc193e63ec7002f09f8909877ea6f
--- /dev/null
+++ b/devices/ide.h
@@ -0,0 +1,6 @@
+#ifndef DEVICES_IDE_H
+#define DEVICES_IDE_H
+
+void ide_init(void);
+
+#endif /* devices/ide.h */
diff --git a/devices/input.c b/devices/input.c
new file mode 100644
index 0000000000000000000000000000000000000000..c7fe9b282e8841df283a3dff675817028176ae34
--- /dev/null
+++ b/devices/input.c
@@ -0,0 +1,50 @@
+#include "devices/input.h"
+
+#include "devices/intq.h"
+#include "devices/serial.h"
+
+#include <debug.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/devices/input.h b/devices/input.h
new file mode 100644
index 0000000000000000000000000000000000000000..730cee92b0651507f14a2dd0f19b446f9850f75a
--- /dev/null
+++ b/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/devices/intq.c b/devices/intq.c
new file mode 100644
index 0000000000000000000000000000000000000000..0f7e9054d1df8bfafb19505758f0cbbe665f59a0
--- /dev/null
+++ b/devices/intq.c
@@ -0,0 +1,107 @@
+#include "devices/intq.h"
+
+#include "threads/thread.h"
+
+#include <debug.h>
+
+static int next(int pos);
+static void wait(struct intq* q, struct thread** waiter);
+static void signal(struct intq* q, struct thread** waiter);
+
+/* Initializes interrupt queue Q. */
+void intq_init(struct intq* q)
+{
+	lock_init(&q->lock);
+	q->not_full = q->not_empty = NULL;
+	q->head = q->tail = 0;
+}
+
+/* Returns true if Q is empty, false otherwise. */
+bool intq_empty(const struct intq* q)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+	return q->head == q->tail;
+}
+
+/* Returns true if Q is full, false otherwise. */
+bool intq_full(const struct intq* q)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+	return next(q->head) == q->tail;
+}
+
+/* Removes a byte from Q and returns it.
+	If Q is empty, sleeps until a byte is added.
+	When called from an interrupt handler, Q must not be empty. */
+uint8_t intq_getc(struct intq* q)
+{
+	uint8_t byte;
+
+	ASSERT(intr_get_level() == INTR_OFF);
+	while (intq_empty(q)) {
+		ASSERT(!intr_context());
+		lock_acquire(&q->lock);
+		wait(q, &q->not_empty);
+		lock_release(&q->lock);
+	}
+
+	byte = q->buf[q->tail];
+	q->tail = next(q->tail);
+	signal(q, &q->not_full);
+	return byte;
+}
+
+/* Adds BYTE to the end of Q.
+	If Q is full, sleeps until a byte is removed.
+	When called from an interrupt handler, Q must not be full. */
+void intq_putc(struct intq* q, uint8_t byte)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+	while (intq_full(q)) {
+		ASSERT(!intr_context());
+		lock_acquire(&q->lock);
+		wait(q, &q->not_full);
+		lock_release(&q->lock);
+	}
+
+	q->buf[q->head] = byte;
+	q->head = next(q->head);
+	signal(q, &q->not_empty);
+}
+
+/* Returns the position after POS within an intq. */
+static int next(int pos)
+{
+	return (pos + 1) % INTQ_BUFSIZE;
+}
+
+/* WAITER must be the address of Q's not_empty or not_full
+	member.  Waits until the given condition is true. */
+static void wait(struct intq* q UNUSED, struct thread** waiter)
+{
+	ASSERT(!intr_context());
+	ASSERT(intr_get_level() == INTR_OFF);
+	ASSERT(
+		 (waiter == &q->not_empty && intq_empty(q))
+		 || (waiter == &q->not_full && intq_full(q)));
+
+	*waiter = thread_current();
+	thread_block();
+}
+
+/* WAITER must be the address of Q's not_empty or not_full
+	member, and the associated condition must be true.  If a
+	thread is waiting for the condition, wakes it up and resets
+	the waiting thread. */
+static void signal(struct intq* q UNUSED, struct thread** waiter)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+	ASSERT(
+		 (waiter == &q->not_empty && !intq_empty(q))
+		 || (waiter == &q->not_full && !intq_full(q)));
+
+	if (*waiter != NULL) {
+		thread_unblock(*waiter);
+		*waiter = NULL;
+	}
+}
diff --git a/devices/intq.h b/devices/intq.h
new file mode 100644
index 0000000000000000000000000000000000000000..953b54f99592219b7e5f4a15925a5c8809b2a033
--- /dev/null
+++ b/devices/intq.h
@@ -0,0 +1,42 @@
+#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/devices/kbd.c b/devices/kbd.c
new file mode 100644
index 0000000000000000000000000000000000000000..09f23f6cd6f3aa1a9b997c5af0c0af731e078dc7
--- /dev/null
+++ b/devices/kbd.c
@@ -0,0 +1,198 @@
+#include "devices/kbd.h"
+
+#include "devices/input.h"
+#include "devices/shutdown.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+
+#include <ctype.h>
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Keyboard data register port. */
+#define DATA_REG 0x60
+
+/* Current state of shift keys.
+	True if depressed, false otherwise. */
+static bool left_shift, right_shift; /* Left and right Shift keys. */
+static bool left_alt, right_alt;		 /* Left and right Alt keys. */
+static bool left_ctrl, right_ctrl;	 /* Left and right Ctl keys. */
+
+/* Status of Caps Lock.
+	True when on, false when off. */
+static bool caps_lock;
+
+/* Number of keys pressed. */
+static int64_t key_cnt;
+
+static intr_handler_func keyboard_interrupt;
+
+/* Initializes the keyboard. */
+void kbd_init(void)
+{
+	intr_register_ext(0x21, keyboard_interrupt, "8042 Keyboard");
+}
+
+/* Prints keyboard statistics. */
+void kbd_print_stats(void)
+{
+	printf("Keyboard: %lld keys pressed\n", key_cnt);
+}
+
+/* Maps a set of contiguous scancodes into characters. */
+struct keymap {
+	uint8_t first_scancode; /* First scancode. */
+	const char* chars;		/* chars[0] has scancode first_scancode,
+										chars[1] has scancode first_scancode + 1,
+										and so on to the end of the string. */
+};
+
+/* Keys that produce the same characters regardless of whether
+	the Shift keys are down.  Case of letters is an exception
+	that we handle elsewhere.  */
+static const struct keymap invariant_keymap[] = {
+	 {0x01, "\033"}, /* Escape. */
+	 {0x0e, "\b"},
+	 {0x0f, "\tQWERTYUIOP"},
+	 {0x1c, "\r"},
+	 {0x1e, "ASDFGHJKL"},
+	 {0x2c, "ZXCVBNM"},
+	 {0x37, "*"},
+	 {0x39, " "},
+	 {0x53, "\177"}, /* Delete. */
+	 {0, NULL},
+};
+
+/* Characters for keys pressed without Shift, for those keys
+	where it matters. */
+static const struct keymap unshifted_keymap[] = {
+	 {0x02, "1234567890-="},
+	 {0x1a, "[]"},
+	 {0x27, ";'`"},
+	 {0x2b, "\\"},
+	 {0x33, ",./"},
+	 {0, NULL},
+};
+
+/* Characters for keys pressed with Shift, for those keys where
+	it matters. */
+static const struct keymap shifted_keymap[] = {
+	 {0x02, "!@#$%^&*()_+"},
+	 {0x1a, "{}"},
+	 {0x27, ":\"~"},
+	 {0x2b, "|"},
+	 {0x33, "<>?"},
+	 {0, NULL},
+};
+
+static bool map_key(const struct keymap[], unsigned scancode, uint8_t*);
+
+static void keyboard_interrupt(struct intr_frame* args UNUSED)
+{
+	/* Status of shift keys. */
+	bool shift = left_shift || right_shift;
+	bool alt = left_alt || right_alt;
+	bool ctrl = left_ctrl || right_ctrl;
+
+	/* Keyboard scancode. */
+	unsigned code;
+
+	/* False if key pressed, true if key released. */
+	bool release;
+
+	/* Character that corresponds to `code'. */
+	uint8_t c;
+
+	/* Read scancode, including second byte if prefix code. */
+	code = inb(DATA_REG);
+	if (code == 0xe0)
+		code = (code << 8) | inb(DATA_REG);
+
+	/* Bit 0x80 distinguishes key press from key release
+		(even if there's a prefix). */
+	release = (code & 0x80) != 0;
+	code &= ~0x80u;
+
+	/* Interpret key. */
+	if (code == 0x3a) {
+		/* Caps Lock. */
+		if (!release)
+			caps_lock = !caps_lock;
+	}
+	else if (
+		 map_key(invariant_keymap, code, &c)
+		 || (!shift && map_key(unshifted_keymap, code, &c))
+		 || (shift && map_key(shifted_keymap, code, &c))) {
+		/* Ordinary character. */
+		if (!release) {
+			/* Reboot if Ctrl+Alt+Del pressed. */
+			if (c == 0177 && ctrl && alt)
+				shutdown_reboot();
+
+			/* Handle Ctrl, Shift.
+				Note that Ctrl overrides Shift. */
+			if (ctrl && c >= 0x40 && c < 0x60) {
+				/* A is 0x41, Ctrl+A is 0x01, etc. */
+				c -= 0x40;
+			}
+			else if (shift == caps_lock)
+				c = tolower(c);
+
+			/* Handle Alt by setting the high bit.
+				This 0x80 is unrelated to the one used to
+				distinguish key press from key release. */
+			if (alt)
+				c += 0x80;
+
+			/* Append to keyboard buffer. */
+			if (!input_full()) {
+				key_cnt++;
+				input_putc(c);
+			}
+		}
+	}
+	else {
+		/* Maps a keycode into a shift state variable. */
+		struct shift_key {
+			unsigned scancode;
+			bool* state_var;
+		};
+
+		/* Table of shift keys. */
+		static const struct shift_key shift_keys[] = {
+			 {0x2a, &left_shift},
+			 {0x36, &right_shift},
+			 {0x38, &left_alt},
+			 {0xe038, &right_alt},
+			 {0x1d, &left_ctrl},
+			 {0xe01d, &right_ctrl},
+			 {0, NULL},
+		};
+
+		const struct shift_key* key;
+
+		/* Scan the table. */
+		for (key = shift_keys; key->scancode != 0; key++)
+			if (key->scancode == code) {
+				*key->state_var = !release;
+				break;
+			}
+	}
+}
+
+/* Scans the array of keymaps K for SCANCODE.
+	If found, sets *C to the corresponding character and returns
+	true.
+	If not found, returns false and C is ignored. */
+static bool map_key(const struct keymap k[], unsigned scancode, uint8_t* c)
+{
+	for (; k->first_scancode != 0; k++)
+		if (scancode >= k->first_scancode
+			 && scancode < k->first_scancode + strlen(k->chars)) {
+			*c = k->chars[scancode - k->first_scancode];
+			return true;
+		}
+
+	return false;
+}
diff --git a/devices/kbd.h b/devices/kbd.h
new file mode 100644
index 0000000000000000000000000000000000000000..5505a5bde6d687b5c4e627b54b92d7e0e2ff86e0
--- /dev/null
+++ b/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/devices/partition.c b/devices/partition.c
new file mode 100644
index 0000000000000000000000000000000000000000..e0cc5727b6795d52f9e113fdac5bd8a75e95e44c
--- /dev/null
+++ b/devices/partition.c
@@ -0,0 +1,335 @@
+#include "devices/partition.h"
+
+#include "devices/block.h"
+#include "threads/malloc.h"
+
+#include <packed.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* A partition of a block device. */
+struct partition {
+	struct block* block;	 /* Underlying block device. */
+	block_sector_t start; /* First sector within device. */
+};
+
+static struct block_operations partition_operations;
+
+static void read_partition_table(
+	 struct block*,
+	 block_sector_t sector,
+	 block_sector_t primary_extended_sector,
+	 int* part_nr);
+static void found_partition(
+	 struct block*,
+	 uint8_t type,
+	 block_sector_t start,
+	 block_sector_t size,
+	 int part_nr);
+static const char* partition_type_name(uint8_t);
+
+/* Scans BLOCK for partitions of interest to Pintos. */
+void partition_scan(struct block* block)
+{
+	int part_nr = 0;
+	read_partition_table(block, 0, 0, &part_nr);
+	if (part_nr == 0)
+		printf("%s: Device contains no partitions\n", block_name(block));
+}
+
+/* Reads the partition table in the given SECTOR of BLOCK and
+	scans it for partitions of interest to Pintos.
+
+	If SECTOR is 0, so that this is the top-level partition table
+	on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful;
+	otherwise, it should designate the sector of the top-level
+	extended partition table that was traversed to arrive at
+	SECTOR, for use in finding logical partitions (see the large
+	comment below).
+
+	PART_NR points to the number of non-empty primary or logical
+	partitions already encountered on BLOCK.  It is incremented as
+	partitions are found. */
+static void read_partition_table(
+	 struct block* block,
+	 block_sector_t sector,
+	 block_sector_t primary_extended_sector,
+	 int* part_nr)
+{
+	/* Format of a partition table entry.  See [Partitions]. */
+	struct partition_table_entry {
+		uint8_t bootable;		 /* 0x00=not bootable, 0x80=bootable. */
+		uint8_t start_chs[3]; /* Encoded starting cylinder, head, sector. */
+		uint8_t type;			 /* Partition type (see partition_type_name). */
+		uint8_t end_chs[3];	 /* Encoded ending cylinder, head, sector. */
+		uint32_t offset;		 /* Start sector offset from partition table. */
+		uint32_t size;			 /* Number of sectors. */
+	} PACKED;
+
+	/* Partition table sector. */
+	struct partition_table {
+		uint8_t loader[446]; /* Loader, in top-level partition table. */
+		struct partition_table_entry partitions[4]; /* Table entries. */
+		uint16_t signature;								  /* Should be 0xaa55. */
+	} PACKED;
+
+	struct partition_table* pt;
+	size_t i;
+
+	/* Check SECTOR validity. */
+	if (sector >= block_size(block)) {
+		printf(
+			 "%s: Partition table at sector %" PRDSNu " past end of device.\n",
+			 block_name(block),
+			 sector);
+		return;
+	}
+
+	/* Read sector. */
+	ASSERT(sizeof *pt == BLOCK_SECTOR_SIZE);
+	pt = malloc(sizeof *pt);
+	if (pt == NULL)
+		PANIC("Failed to allocate memory for partition table.");
+	block_read(block, 0, pt);
+
+	/* Check signature. */
+	if (pt->signature != 0xaa55) {
+		if (primary_extended_sector == 0)
+			printf("%s: Invalid partition table signature\n", block_name(block));
+		else
+			printf(
+				 "%s: Invalid extended partition table in sector %" PRDSNu "\n",
+				 block_name(block),
+				 sector);
+		free(pt);
+		return;
+	}
+
+	/* Parse partitions. */
+	for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++) {
+		struct partition_table_entry* e = &pt->partitions[i];
+
+		if (e->size == 0 || e->type == 0) {
+			/* Ignore empty partition. */
+		}
+		else if (
+			 e->type == 0x05		/* Extended partition. */
+			 || e->type == 0x0f	/* Windows 98 extended partition. */
+			 || e->type == 0x85	/* Linux extended partition. */
+			 || e->type == 0xc5) /* DR-DOS extended partition. */
+		{
+			printf(
+				 "%s: Extended partition in sector %" PRDSNu "\n",
+				 block_name(block),
+				 sector);
+
+			/* The interpretation of the offset field for extended
+				partitions is bizarre.  When the extended partition
+				table entry is in the master boot record, that is,
+				the device's primary partition table in sector 0, then
+				the offset is an absolute sector number.  Otherwise,
+				no matter how deep the partition table we're reading
+				is nested, the offset is relative to the start of
+				the extended partition that the MBR points to. */
+			if (sector == 0)
+				read_partition_table(block, e->offset, e->offset, part_nr);
+			else
+				read_partition_table(
+					 block,
+					 e->offset + primary_extended_sector,
+					 primary_extended_sector,
+					 part_nr);
+		}
+		else {
+			++*part_nr;
+
+			found_partition(block, e->type, e->offset + sector, e->size, *part_nr);
+		}
+	}
+
+	free(pt);
+}
+
+/* We have found a primary or logical partition of the given TYPE
+	on BLOCK, starting at sector START and continuing for SIZE
+	sectors, which we are giving the partition number PART_NR.
+	Check whether this is a partition of interest to Pintos, and
+	if so then add it to the proper element of partitions[]. */
+static void found_partition(
+	 struct block* block,
+	 uint8_t part_type,
+	 block_sector_t start,
+	 block_sector_t size,
+	 int part_nr)
+{
+	if (start >= block_size(block))
+		printf(
+			 "%s%d: Partition starts past end of device (sector %" PRDSNu ")\n",
+			 block_name(block),
+			 part_nr,
+			 start);
+	else if (start + size < start || start + size > block_size(block))
+		printf(
+			 "%s%d: Partition end (%" PRDSNu ") past end of device (%" PRDSNu ")\n",
+			 block_name(block),
+			 part_nr,
+			 start + size,
+			 block_size(block));
+	else {
+		enum block_type type
+			 = (part_type == 0x20	? BLOCK_KERNEL
+				 : part_type == 0x21 ? BLOCK_FILESYS
+				 : part_type == 0x22 ? BLOCK_SCRATCH
+				 : part_type == 0x23 ? BLOCK_SWAP
+											: BLOCK_FOREIGN);
+		struct partition* p;
+		char extra_info[128];
+		char name[16];
+
+		p = malloc(sizeof *p);
+		if (p == NULL)
+			PANIC("Failed to allocate memory for partition descriptor");
+		p->block = block;
+		p->start = start;
+
+		snprintf(name, sizeof name, "%s%d", block_name(block), part_nr);
+		snprintf(
+			 extra_info,
+			 sizeof extra_info,
+			 "%s (%02x)",
+			 partition_type_name(part_type),
+			 part_type);
+		block_register(name, type, extra_info, size, &partition_operations, p);
+	}
+}
+
+/* Returns a human-readable name for the given partition TYPE. */
+static const char* partition_type_name(uint8_t type)
+{
+	/* Name of each known type of partition.
+		From util-linux-2.12r/fdisk/i386_sys_types.c.
+		This initializer makes use of a C99 feature that allows
+		array elements to be initialized by index. */
+	static const char* type_names[256] = {
+		 [0x00] = "Empty",
+		 [0x01] = "FAT12",
+		 [0x02] = "XENIX root",
+		 [0x03] = "XENIX usr",
+		 [0x04] = "FAT16 <32M",
+		 [0x05] = "Extended",
+		 [0x06] = "FAT16",
+		 [0x07] = "HPFS/NTFS",
+		 [0x08] = "AIX",
+		 [0x09] = "AIX bootable",
+		 [0x0a] = "OS/2 Boot Manager",
+		 [0x0b] = "W95 FAT32",
+		 [0x0c] = "W95 FAT32 (LBA)",
+		 [0x0e] = "W95 FAT16 (LBA)",
+		 [0x0f] = "W95 Ext'd (LBA)",
+		 [0x10] = "OPUS",
+		 [0x11] = "Hidden FAT12",
+		 [0x12] = "Compaq diagnostics",
+		 [0x14] = "Hidden FAT16 <32M",
+		 [0x16] = "Hidden FAT16",
+		 [0x17] = "Hidden HPFS/NTFS",
+		 [0x18] = "AST SmartSleep",
+		 [0x1b] = "Hidden W95 FAT32",
+		 [0x1c] = "Hidden W95 FAT32 (LBA)",
+		 [0x1e] = "Hidden W95 FAT16 (LBA)",
+		 [0x20] = "Pintos OS kernel",
+		 [0x21] = "Pintos file system",
+		 [0x22] = "Pintos scratch",
+		 [0x23] = "Pintos swap",
+		 [0x24] = "NEC DOS",
+		 [0x39] = "Plan 9",
+		 [0x3c] = "PartitionMagic recovery",
+		 [0x40] = "Venix 80286",
+		 [0x41] = "PPC PReP Boot",
+		 [0x42] = "SFS",
+		 [0x4d] = "QNX4.x",
+		 [0x4e] = "QNX4.x 2nd part",
+		 [0x4f] = "QNX4.x 3rd part",
+		 [0x50] = "OnTrack DM",
+		 [0x51] = "OnTrack DM6 Aux1",
+		 [0x52] = "CP/M",
+		 [0x53] = "OnTrack DM6 Aux3",
+		 [0x54] = "OnTrackDM6",
+		 [0x55] = "EZ-Drive",
+		 [0x56] = "Golden Bow",
+		 [0x5c] = "Priam Edisk",
+		 [0x61] = "SpeedStor",
+		 [0x63] = "GNU HURD or SysV",
+		 [0x64] = "Novell Netware 286",
+		 [0x65] = "Novell Netware 386",
+		 [0x70] = "DiskSecure Multi-Boot",
+		 [0x75] = "PC/IX",
+		 [0x80] = "Old Minix",
+		 [0x81] = "Minix / old Linux",
+		 [0x82] = "Linux swap / Solaris",
+		 [0x83] = "Linux",
+		 [0x84] = "OS/2 hidden C: drive",
+		 [0x85] = "Linux extended",
+		 [0x86] = "NTFS volume set",
+		 [0x87] = "NTFS volume set",
+		 [0x88] = "Linux plaintext",
+		 [0x8e] = "Linux LVM",
+		 [0x93] = "Amoeba",
+		 [0x94] = "Amoeba BBT",
+		 [0x9f] = "BSD/OS",
+		 [0xa0] = "IBM Thinkpad hibernation",
+		 [0xa5] = "FreeBSD",
+		 [0xa6] = "OpenBSD",
+		 [0xa7] = "NeXTSTEP",
+		 [0xa8] = "Darwin UFS",
+		 [0xa9] = "NetBSD",
+		 [0xab] = "Darwin boot",
+		 [0xb7] = "BSDI fs",
+		 [0xb8] = "BSDI swap",
+		 [0xbb] = "Boot Wizard hidden",
+		 [0xbe] = "Solaris boot",
+		 [0xbf] = "Solaris",
+		 [0xc1] = "DRDOS/sec (FAT-12)",
+		 [0xc4] = "DRDOS/sec (FAT-16 < 32M)",
+		 [0xc6] = "DRDOS/sec (FAT-16)",
+		 [0xc7] = "Syrinx",
+		 [0xda] = "Non-FS data",
+		 [0xdb] = "CP/M / CTOS / ...",
+		 [0xde] = "Dell Utility",
+		 [0xdf] = "BootIt",
+		 [0xe1] = "DOS access",
+		 [0xe3] = "DOS R/O",
+		 [0xe4] = "SpeedStor",
+		 [0xeb] = "BeOS fs",
+		 [0xee] = "EFI GPT",
+		 [0xef] = "EFI (FAT-12/16/32)",
+		 [0xf0] = "Linux/PA-RISC boot",
+		 [0xf1] = "SpeedStor",
+		 [0xf4] = "SpeedStor",
+		 [0xf2] = "DOS secondary",
+		 [0xfd] = "Linux raid autodetect",
+		 [0xfe] = "LANstep",
+		 [0xff] = "BBT",
+	};
+
+	return type_names[type] != NULL ? type_names[type] : "Unknown";
+}
+
+/* Reads sector SECTOR from partition P into BUFFER, which must
+	have room for BLOCK_SECTOR_SIZE bytes. */
+static void partition_read(void* p_, block_sector_t sector, void* buffer)
+{
+	struct partition* p = p_;
+	block_read(p->block, p->start + sector, buffer);
+}
+
+/* Write sector SECTOR to partition P from BUFFER, which must
+	contain BLOCK_SECTOR_SIZE bytes.  Returns after the block has
+	acknowledged receiving the data. */
+static void partition_write(void* p_, block_sector_t sector, const void* buffer)
+{
+	struct partition* p = p_;
+	block_write(p->block, p->start + sector, buffer);
+}
+
+static struct block_operations partition_operations = {partition_read, partition_write};
diff --git a/devices/partition.h b/devices/partition.h
new file mode 100644
index 0000000000000000000000000000000000000000..57d5f7d1028562ab1b1e2a275bdaa3530ed35c7d
--- /dev/null
+++ b/devices/partition.h
@@ -0,0 +1,8 @@
+#ifndef DEVICES_PARTITION_H
+#define DEVICES_PARTITION_H
+
+struct block;
+
+void partition_scan(struct block*);
+
+#endif /* devices/partition.h */
diff --git a/devices/pit.c b/devices/pit.c
new file mode 100644
index 0000000000000000000000000000000000000000..927779a1eddd0ce470a226ecf69a2cf5a2a5ecf7
--- /dev/null
+++ b/devices/pit.c
@@ -0,0 +1,82 @@
+#include "devices/pit.h"
+
+#include "threads/interrupt.h"
+#include "threads/io.h"
+
+#include <debug.h>
+#include <stdint.h>
+
+/* Interface to 8254 Programmable Interrupt Timer (PIT).
+	Refer to [8254] for details. */
+
+/* 8254 registers. */
+#define PIT_PORT_CONTROL			 0x43					  /* Control port. */
+#define PIT_PORT_COUNTER(CHANNEL) (0x40 + (CHANNEL)) /* Counter port. */
+
+/* PIT cycles per second. */
+#define PIT_HZ 1193180
+
+/* Configure the given CHANNEL in the PIT.  In a PC, the PIT's
+	three output channels are hooked up like this:
+
+	  - Channel 0 is connected to interrupt line 0, so that it can
+		 be used as a periodic timer interrupt, as implemented in
+		 Pintos in devices/timer.c.
+
+	  - Channel 1 is used for dynamic RAM refresh (in older PCs).
+		 No good can come of messing with this.
+
+	  - Channel 2 is connected to the PC speaker, so that it can
+		 be used to play a tone, as implemented in Pintos in
+		 devices/speaker.c.
+
+	MODE specifies the form of output:
+
+	  - Mode 2 is a periodic pulse: the channel's output is 1 for
+		 most of the period, but drops to 0 briefly toward the end
+		 of the period.  This is useful for hooking up to an
+		 interrupt controller to generate a periodic interrupt.
+
+	  - Mode 3 is a square wave: for the first half of the period
+		 it is 1, for the second half it is 0.  This is useful for
+		 generating a tone on a speaker.
+
+	  - Other modes are less useful.
+
+	FREQUENCY is the number of periods per second, in Hz. */
+void pit_configure_channel(int channel, int mode, int frequency)
+{
+	uint16_t count;
+	enum intr_level old_level;
+
+	ASSERT(channel == 0 || channel == 2);
+	ASSERT(mode == 2 || mode == 3);
+
+	/* Convert FREQUENCY to a PIT counter value.  The PIT has a
+		clock that runs at PIT_HZ cycles per second.  We must
+		translate FREQUENCY into a number of these cycles. */
+	if (frequency < 19) {
+		/* Frequency is too low: the quotient would overflow the
+			16-bit counter.  Force it to 0, which the PIT treats as
+			65536, the highest possible count.  This yields a 18.2
+			Hz timer, approximately. */
+		count = 0;
+	}
+	else if (frequency > PIT_HZ) {
+		/* Frequency is too high: the quotient would underflow to
+			0, which the PIT would interpret as 65536.  A count of 1
+			is illegal in mode 2, so we force it to 2, which yields
+			a 596.590 kHz timer, approximately.  (This timer rate is
+			probably too fast to be useful anyhow.) */
+		count = 2;
+	}
+	else
+		count = (PIT_HZ + frequency / 2) / frequency;
+
+	/* Configure the PIT mode and load its counters. */
+	old_level = intr_disable();
+	outb(PIT_PORT_CONTROL, (channel << 6) | 0x30 | (mode << 1));
+	outb(PIT_PORT_COUNTER(channel), count);
+	outb(PIT_PORT_COUNTER(channel), count >> 8);
+	intr_set_level(old_level);
+}
diff --git a/devices/pit.h b/devices/pit.h
new file mode 100644
index 0000000000000000000000000000000000000000..a95f95cd234b5f6e067fb63c85d0fe64ee770849
--- /dev/null
+++ b/devices/pit.h
@@ -0,0 +1,8 @@
+#ifndef DEVICES_PIT_H
+#define DEVICES_PIT_H
+
+#include <stdint.h>
+
+void pit_configure_channel(int channel, int mode, int frequency);
+
+#endif /* devices/pit.h */
diff --git a/devices/rtc.c b/devices/rtc.c
new file mode 100644
index 0000000000000000000000000000000000000000..b372fa73e598559d2f391c6d25ffa9a11f74e8a6
--- /dev/null
+++ b/devices/rtc.c
@@ -0,0 +1,107 @@
+#include "devices/rtc.h"
+
+#include "threads/io.h"
+
+#include <round.h>
+#include <stdio.h>
+
+/* This code is an interface to the MC146818A-compatible real
+	time clock found on PC motherboards.  See [MC146818A] for
+	hardware details. */
+
+/* I/O register addresses. */
+#define CMOS_REG_SET 0x70 /* Selects CMOS register exposed by REG_IO. */
+#define CMOS_REG_IO	0x71 /* Contains the selected data byte. */
+
+/* Indexes of CMOS registers with real-time clock functions.
+	Note that all of these registers are in BCD format,
+	so that 0x59 means 59, not 89. */
+#define RTC_REG_SEC	0 /* Second: 0x00...0x59. */
+#define RTC_REG_MIN	2 /* Minute: 0x00...0x59. */
+#define RTC_REG_HOUR 4 /* Hour: 0x00...0x23. */
+#define RTC_REG_MDAY 7 /* Day of the month: 0x01...0x31. */
+#define RTC_REG_MON	8 /* Month: 0x01...0x12. */
+#define RTC_REG_YEAR 9 /* Year: 0x00...0x99. */
+
+/* Indexes of CMOS control registers. */
+#define RTC_REG_A 0x0a /* Register A: update-in-progress. */
+#define RTC_REG_B 0x0b /* Register B: 24/12 hour time, irq enables. */
+#define RTC_REG_C 0x0c /* Register C: pending interrupts. */
+#define RTC_REG_D 0x0d /* Register D: valid time? */
+
+/* Register A. */
+#define RTCSA_UIP 0x80 /* Set while time update in progress. */
+
+/* Register B. */
+#define RTCSB_SET	 0x80 /* Disables update to let time be set. */
+#define RTCSB_DM	 0x04 /* 0 = BCD time format, 1 = binary format. */
+#define RTCSB_24HR 0x02 /* 0 = 12-hour format, 1 = 24-hour format. */
+
+static int bcd_to_bin(uint8_t);
+static uint8_t cmos_read(uint8_t index);
+
+/* Returns number of seconds since Unix epoch of January 1,
+	1970. */
+time_t rtc_get_time(void)
+{
+	static const int days_per_month[12]
+		 = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+	int sec, min, hour, mday, mon, year;
+	time_t time;
+	int i;
+
+	/* Get time components.
+
+		We repeatedly read the time until it is stable from one read
+		to another, in case we start our initial read in the middle
+		of an update.  This strategy is not recommended by the
+		MC146818A datasheet, but it is simpler than any of their
+		suggestions and, furthermore, it is also used by Linux.
+
+		The MC146818A can be configured for BCD or binary format,
+		but for historical reasons everyone always uses BCD format
+		except on obscure non-PC platforms, so we don't bother
+		trying to detect the format in use. */
+	do {
+		sec = bcd_to_bin(cmos_read(RTC_REG_SEC));
+		min = bcd_to_bin(cmos_read(RTC_REG_MIN));
+		hour = bcd_to_bin(cmos_read(RTC_REG_HOUR));
+		mday = bcd_to_bin(cmos_read(RTC_REG_MDAY));
+		mon = bcd_to_bin(cmos_read(RTC_REG_MON));
+		year = bcd_to_bin(cmos_read(RTC_REG_YEAR));
+	} while (sec != bcd_to_bin(cmos_read(RTC_REG_SEC)));
+
+	/* Translate years-since-1900 into years-since-1970.
+		If it's before the epoch, assume that it has passed 2000.
+		This will break at 2070, but that's long after our 31-bit
+		time_t breaks in 2038. */
+	if (year < 70)
+		year += 100;
+	year -= 70;
+
+	/* Break down all components into seconds. */
+	time = (year * 365 + DIV_ROUND_UP(year - 2, 4)) * 24 * 60 * 60;
+	for (i = 1; i < mon; i++) time += days_per_month[i - 1] * 24 * 60 * 60;
+	if (mon > 2 && year % 4 == 2)
+		time += 24 * 60 * 60;
+	time += (mday - 1) * 24 * 60 * 60;
+	time += hour * 60 * 60;
+	time += min * 60;
+	time += sec;
+
+	return time;
+}
+
+/* Returns the integer value of the given BCD byte. */
+static int bcd_to_bin(uint8_t x)
+{
+	return (x & 0x0f) + ((x >> 4) * 10);
+}
+
+/* Reads a byte from the CMOS register with the given INDEX and
+	returns the byte read. */
+static uint8_t cmos_read(uint8_t index)
+{
+	outb(CMOS_REG_SET, index);
+	return inb(CMOS_REG_IO);
+}
diff --git a/devices/rtc.h b/devices/rtc.h
new file mode 100644
index 0000000000000000000000000000000000000000..c0be1c2146306f936866f7cb384b74d659ce5919
--- /dev/null
+++ b/devices/rtc.h
@@ -0,0 +1,8 @@
+#ifndef RTC_H
+#define RTC_H
+
+typedef unsigned long time_t;
+
+time_t rtc_get_time(void);
+
+#endif
diff --git a/devices/serial.c b/devices/serial.c
new file mode 100644
index 0000000000000000000000000000000000000000..71ba35ebaabaf758d9810f9cb1e6d1050c01ced9
--- /dev/null
+++ b/devices/serial.c
@@ -0,0 +1,215 @@
+#include "devices/serial.h"
+
+#include "devices/input.h"
+#include "devices/intq.h"
+#include "devices/timer.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <debug.h>
+
+/* Register definitions for the 16550A UART used in PCs.
+	The 16550A has a lot more going on than shown here, but this
+	is all we need.
+
+	Refer to [PC16650D] for hardware information. */
+
+/* I/O port base address for the first serial port. */
+#define IO_BASE 0x3f8
+
+/* DLAB=0 registers. */
+#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */
+#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */
+#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */
+
+/* DLAB=1 registers. */
+#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */
+#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */
+
+/* DLAB-insensitive registers. */
+#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */
+#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
+#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
+#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
+#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
+
+/* Interrupt Enable Register bits. */
+#define IER_RECV 0x01 /* Interrupt when data received. */
+#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */
+
+/* Line Control Register bits. */
+#define LCR_N81  0x03 /* No parity, 8 data bits, 1 stop bit. */
+#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */
+
+/* MODEM Control Register. */
+#define MCR_OUT2 0x08 /* Output line 2. */
+
+/* Line Status Register. */
+#define LSR_DR	  0x01 /* Data Ready: received data byte is in RBR. */
+#define LSR_THRE 0x20 /* THR Empty. */
+
+/* Transmission mode. */
+static enum { UNINIT, POLL, QUEUE } mode;
+
+/* Data to be transmitted. */
+static struct intq txq;
+
+static void set_serial(int bps);
+static void putc_poll(uint8_t);
+static void write_ier(void);
+static intr_handler_func serial_interrupt;
+
+/* Initializes the serial port device for polling mode.
+	Polling mode busy-waits for the serial port to become free
+	before writing to it.  It's slow, but until interrupts have
+	been initialized it's all we can do. */
+static void init_poll(void)
+{
+	ASSERT(mode == UNINIT);
+	outb(IER_REG, 0);			 /* Turn off all interrupts. */
+	outb(FCR_REG, 0);			 /* Disable FIFO. */
+	set_serial(9600);			 /* 9.6 kbps, N-8-1. */
+	outb(MCR_REG, MCR_OUT2); /* Required to enable interrupts. */
+	intq_init(&txq);
+	mode = POLL;
+}
+
+/* Initializes the serial port device for queued interrupt-driven
+	I/O.  With interrupt-driven I/O we don't waste CPU time
+	waiting for the serial device to become ready. */
+void serial_init_queue(void)
+{
+	enum intr_level old_level;
+
+	if (mode == UNINIT)
+		init_poll();
+	ASSERT(mode == POLL);
+
+	intr_register_ext(0x20 + 4, serial_interrupt, "serial");
+	mode = QUEUE;
+	old_level = intr_disable();
+	write_ier();
+	intr_set_level(old_level);
+}
+
+/* Sends BYTE to the serial port. */
+void serial_putc(uint8_t byte)
+{
+	enum intr_level old_level = intr_disable();
+
+	if (mode != QUEUE) {
+		/* If we're not set up for interrupt-driven I/O yet,
+			use dumb polling to transmit a byte. */
+		if (mode == UNINIT)
+			init_poll();
+		putc_poll(byte);
+	}
+	else {
+		/* Otherwise, queue a byte and update the interrupt enable
+			register. */
+		if (old_level == INTR_OFF && intq_full(&txq)) {
+			/* Interrupts are off and the transmit queue is full.
+				If we wanted to wait for the queue to empty,
+				we'd have to reenable interrupts.
+				That's impolite, so we'll send a character via
+				polling instead. */
+			putc_poll(intq_getc(&txq));
+		}
+
+		intq_putc(&txq, byte);
+		write_ier();
+	}
+
+	intr_set_level(old_level);
+}
+
+/* Flushes anything in the serial buffer out the port in polling
+	mode. */
+void serial_flush(void)
+{
+	enum intr_level old_level = intr_disable();
+	while (!intq_empty(&txq)) putc_poll(intq_getc(&txq));
+	intr_set_level(old_level);
+}
+
+/* The fullness of the input buffer may have changed.  Reassess
+	whether we should block receive interrupts.
+	Called by the input buffer routines when characters are added
+	to or removed from the buffer. */
+void serial_notify(void)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+	if (mode == QUEUE)
+		write_ier();
+}
+
+/* Configures the serial port for BPS bits per second. */
+static void set_serial(int bps)
+{
+	int base_rate = 1843200 / 16;			/* Base rate of 16550A, in Hz. */
+	uint16_t divisor = base_rate / bps; /* Clock rate divisor. */
+
+	ASSERT(bps >= 300 && bps <= 115200);
+
+	/* Enable DLAB. */
+	outb(LCR_REG, LCR_N81 | LCR_DLAB);
+
+	/* Set data rate. */
+	outb(LS_REG, divisor & 0xff);
+	outb(MS_REG, divisor >> 8);
+
+	/* Reset DLAB. */
+	outb(LCR_REG, LCR_N81);
+}
+
+/* Update interrupt enable register. */
+static void write_ier(void)
+{
+	uint8_t ier = 0;
+
+	ASSERT(intr_get_level() == INTR_OFF);
+
+	/* Enable transmit interrupt if we have any characters to
+		transmit. */
+	if (!intq_empty(&txq))
+		ier |= IER_XMIT;
+
+	/* Enable receive interrupt if we have room to store any
+		characters we receive. */
+	if (!input_full())
+		ier |= IER_RECV;
+
+	outb(IER_REG, ier);
+}
+
+/* Polls the serial port until it's ready,
+	and then transmits BYTE. */
+static void putc_poll(uint8_t byte)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+
+	while ((inb(LSR_REG) & LSR_THRE) == 0) continue;
+	outb(THR_REG, byte);
+}
+
+/* Serial interrupt handler. */
+static void serial_interrupt(struct intr_frame* f UNUSED)
+{
+	/* Inquire about interrupt in UART.  Without this, we can
+		occasionally miss an interrupt running under QEMU. */
+	inb(IIR_REG);
+
+	/* As long as we have room to receive a byte, and the hardware
+		has a byte for us, receive a byte.  */
+	while (!input_full() && (inb(LSR_REG) & LSR_DR) != 0) input_putc(inb(RBR_REG));
+
+	/* As long as we have a byte to transmit, and the hardware is
+		ready to accept a byte for transmission, transmit a byte. */
+	while (!intq_empty(&txq) && (inb(LSR_REG) & LSR_THRE) != 0)
+		outb(THR_REG, intq_getc(&txq));
+
+	/* Update interrupt enable register based on queue status. */
+	write_ier();
+}
diff --git a/devices/serial.h b/devices/serial.h
new file mode 100644
index 0000000000000000000000000000000000000000..42601fac582bd0aa16519c0c92dba17d2be84514
--- /dev/null
+++ b/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/devices/shutdown.c b/devices/shutdown.c
new file mode 100644
index 0000000000000000000000000000000000000000..e95ed6e73dcdd5999c293c9095d2b9db3524aca8
--- /dev/null
+++ b/devices/shutdown.c
@@ -0,0 +1,135 @@
+#include "devices/shutdown.h"
+
+#include "devices/kbd.h"
+#include "devices/serial.h"
+#include "devices/timer.h"
+#include "threads/io.h"
+#include "threads/thread.h"
+
+#include <console.h>
+#include <stdio.h>
+#ifdef USERPROG
+#include "userprog/exception.h"
+#endif
+#ifdef FILESYS
+#include "devices/block.h"
+#include "filesys/filesys.h"
+#endif
+
+/* Keyboard control register port. */
+#define CONTROL_REG 0x64
+
+/* How to shut down when shutdown() is called. */
+static enum shutdown_type how = SHUTDOWN_NONE;
+
+static void print_stats(void);
+
+/* Shuts down the machine in the way configured by
+	shutdown_configure().  If the shutdown type is SHUTDOWN_NONE
+	(which is the default), returns without doing anything. */
+void shutdown(void)
+{
+	switch (how) {
+		case SHUTDOWN_POWER_OFF:
+			shutdown_power_off();
+			break;
+
+		case SHUTDOWN_REBOOT:
+			shutdown_reboot();
+			break;
+
+		default:
+			/* Nothing to do. */
+			break;
+	}
+}
+
+/* Sets TYPE as the way that machine will shut down when Pintos
+	execution is complete. */
+void shutdown_configure(enum shutdown_type type)
+{
+	how = type;
+}
+
+/* Reboots the machine via the keyboard controller. */
+void shutdown_reboot(void)
+{
+	printf("Rebooting...\n");
+
+	/* See [kbd] for details on how to program the keyboard
+	 * controller. */
+	for (;;) {
+		int i;
+
+		/* Poll keyboard controller's status byte until
+		 * 'input buffer empty' is reported. */
+		for (i = 0; i < 0x10000; i++) {
+			if ((inb(CONTROL_REG) & 0x02) == 0)
+				break;
+			timer_udelay(2);
+		}
+
+		timer_udelay(50);
+
+		/* Pulse bit 0 of the output port P2 of the keyboard controller.
+		 * This will reset the CPU. */
+		outb(CONTROL_REG, 0xfe);
+		timer_udelay(50);
+	}
+}
+
+/* Powers down the machine we're running on,
+	as long as we're running on Bochs or QEMU. */
+void shutdown_power_off(void)
+{
+	const char s[] = "Shutdown";
+	const char* p;
+
+#ifdef FILESYS
+	filesys_done();
+#endif
+
+	print_stats();
+
+	printf("Powering off...\n");
+	serial_flush();
+
+	/* ACPI power-off */
+	outw(0xB004, 0x2000);
+
+	/* This is a special power-off sequence supported by Bochs and
+		QEMU, but not by physical hardware. */
+	for (p = s; *p != '\0'; p++) outb(0x8900, *p);
+
+	/* For newer versions of qemu, you must run with -device
+	 * isa-debug-exit, which exits on any write to an IO port (by
+	 * default 0x501).  Qemu's exit code is double the value plus one,
+	 * so there is no way to exit cleanly.  We use 0x31 which should
+	 * result in a qemu exit code of 0x63.  */
+	outb(0x501, 0x31);
+
+	/* This will power off a VMware VM if "gui.exitOnCLIHLT = TRUE"
+		is set in its configuration file.  (The "pintos" script does
+		that automatically.)  */
+	asm volatile("cli; hlt" : : : "memory");
+
+	/* None of those worked. */
+	printf("still running...\n");
+	for (;;)
+		;
+}
+
+/* Print statistics about Pintos execution. */
+static void print_stats(void)
+{
+	timer_print_stats();
+	thread_print_stats();
+#ifdef FILESYS
+	block_print_stats();
+#endif
+	console_print_stats();
+	kbd_print_stats();
+#ifdef USERPROG
+	exception_print_stats();
+#endif
+}
diff --git a/devices/shutdown.h b/devices/shutdown.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5198abd969144d73c5f7f09780ad86861b82ce0
--- /dev/null
+++ b/devices/shutdown.h
@@ -0,0 +1,18 @@
+#ifndef DEVICES_SHUTDOWN_H
+#define DEVICES_SHUTDOWN_H
+
+#include <debug.h>
+
+/* How to shut down when Pintos has nothing left to do. */
+enum shutdown_type {
+	SHUTDOWN_NONE,		  /* Loop forever. */
+	SHUTDOWN_POWER_OFF, /* Power off the machine (if possible). */
+	SHUTDOWN_REBOOT,	  /* Reboot the machine (if possible). */
+};
+
+void shutdown(void);
+void shutdown_configure(enum shutdown_type);
+void shutdown_reboot(void) NO_RETURN;
+void shutdown_power_off(void) NO_RETURN;
+
+#endif /* devices/shutdown.h */
diff --git a/devices/speaker.c b/devices/speaker.c
new file mode 100644
index 0000000000000000000000000000000000000000..152a4f7054eb0166ba890f9f0848f519393802b0
--- /dev/null
+++ b/devices/speaker.c
@@ -0,0 +1,63 @@
+#include "devices/speaker.h"
+
+#include "devices/pit.h"
+#include "devices/timer.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+
+/* Speaker port enable I/O register. */
+#define SPEAKER_PORT_GATE 0x61
+
+/* Speaker port enable bits. */
+#define SPEAKER_GATE_ENABLE 0x03
+
+/* Sets the PC speaker to emit a tone at the given FREQUENCY, in
+	Hz. */
+void speaker_on(int frequency)
+{
+	if (frequency >= 20 && frequency <= 20000) {
+		/* Set the timer channel that's connected to the speaker to
+			output a square wave at the given FREQUENCY, then
+			connect the timer channel output to the speaker. */
+		enum intr_level old_level = intr_disable();
+		pit_configure_channel(2, 3, frequency);
+		outb(SPEAKER_PORT_GATE, inb(SPEAKER_PORT_GATE) | SPEAKER_GATE_ENABLE);
+		intr_set_level(old_level);
+	}
+	else {
+		/* FREQUENCY is outside the range of normal human hearing.
+			Just turn off the speaker. */
+		speaker_off();
+	}
+}
+
+/* Turn off the PC speaker, by disconnecting the timer channel's
+	output from the speaker. */
+void speaker_off(void)
+{
+	enum intr_level old_level = intr_disable();
+	outb(SPEAKER_PORT_GATE, inb(SPEAKER_PORT_GATE) & ~SPEAKER_GATE_ENABLE);
+	intr_set_level(old_level);
+}
+
+/* Briefly beep the PC speaker. */
+void speaker_beep(void)
+{
+	/* Only attempt to beep the speaker if interrupts are enabled,
+		because we don't want to freeze the machine during the beep.
+		We could add a hook to the timer interrupt to avoid that
+		problem, but then we'd risk failing to ever stop the beep if
+		Pintos crashes for some unrelated reason.  There's nothing
+		more annoying than a machine whose beeping you can't stop
+		without a power cycle.
+
+		We can't just enable interrupts while we sleep.  For one
+		thing, we get called (indirectly) from printf, which should
+		always work, even during boot before we're ready to enable
+		interrupts. */
+	if (intr_get_level() == INTR_ON) {
+		speaker_on(440);
+		timer_msleep(250);
+		speaker_off();
+	}
+}
diff --git a/devices/speaker.h b/devices/speaker.h
new file mode 100644
index 0000000000000000000000000000000000000000..e69d3b49af25777359bf0708214ffce4343fe2fb
--- /dev/null
+++ b/devices/speaker.h
@@ -0,0 +1,8 @@
+#ifndef DEVICES_SPEAKER_H
+#define DEVICES_SPEAKER_H
+
+void speaker_on(int frequency);
+void speaker_off(void);
+void speaker_beep(void);
+
+#endif /* devices/speaker.h */
diff --git a/devices/timer.c b/devices/timer.c
new file mode 100644
index 0000000000000000000000000000000000000000..b92f88fb6549bbc06940ffba1df7e1aee7f77c43
--- /dev/null
+++ b/devices/timer.c
@@ -0,0 +1,259 @@
+#include "devices/timer.h"
+
+#include "devices/pit.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <debug.h>
+#include <inttypes.h>
+#include <round.h>
+#include <stdio.h>
+#include "list.h"
+#include <stdlib.h>
+
+/* See [8254] for hardware details of the 8254 timer chip. */
+
+static uint16_t TIMER_FREQ = 0;
+
+
+/* Number of timer ticks since OS booted. */
+static int64_t ticks;
+
+/* Number of loops per timer tick.
+	Initialized by timer_calibrate(). */
+static unsigned loops_per_tick;
+
+static intr_handler_func timer_interrupt;
+static bool too_many_loops(unsigned loops);
+static void busy_wait(int64_t loops);
+static void real_time_sleep(int64_t num, int32_t denom);
+static void real_time_delay(int64_t num, int32_t denom);
+
+
+/* Sets up the timer to interrupt TIMER_FREQ times per second,
+	and registers the corresponding interrupt. */
+void timer_init(const uint16_t timer_freq)
+{
+	
+	list_init(&qeue_timer);
+	TIMER_FREQ = timer_freq;
+	pit_configure_channel(0, 2, TIMER_FREQ);
+	intr_register_ext(0x20, timer_interrupt, "8254 Timer");
+}
+
+/* Calibrates loops_per_tick, used to implement brief delays. */
+void timer_calibrate(void)
+{
+	unsigned high_bit, test_bit;
+
+	ASSERT(intr_get_level() == INTR_ON);
+	printf("Calibrating timer...  ");
+
+	/* Approximate loops_per_tick as the largest power-of-two
+		still less than one timer tick. */
+	loops_per_tick = 1u << 10;
+	while (!too_many_loops(loops_per_tick << 1)) {
+		loops_per_tick <<= 1;
+		ASSERT(loops_per_tick != 0);
+	}
+
+	/* Refine the next 8 bits of loops_per_tick. */
+	high_bit = loops_per_tick;
+	for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1)
+		if (!too_many_loops(loops_per_tick | test_bit))
+			loops_per_tick |= test_bit;
+
+	printf("%'" PRIu64 " loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ);
+}
+
+/* Returns the number of timer ticks since the OS booted. */
+int64_t timer_ticks(void)
+{
+	enum intr_level old_level = intr_disable();
+	int64_t t = ticks;
+	intr_set_level(old_level);
+	return t;
+}
+
+/* Returns the number of timer ticks elapsed since THEN, which
+	should be a value once returned by timer_ticks(). */
+int64_t timer_elapsed(int64_t then)
+{
+	return timer_ticks() - then;
+}
+struct timer_thread{
+	struct list_elem  thread_element;
+	int64_t thread_ticks;
+	struct semaphore sema;
+};
+
+bool less(const struct list_elem *a, const struct list_elem *b, void *aux )
+{
+    struct timer_thread *elem_a = list_entry(a, struct timer_thread, thread_element);
+    struct timer_thread *elem_b = list_entry(b, struct timer_thread, thread_element);
+    return elem_a->thread_ticks < elem_b->thread_ticks;
+}
+
+/* Timer interrupt handler. */
+static void timer_interrupt(struct intr_frame* args UNUSED)
+{
+	ticks++;
+	thread_tick();
+/////////////our code////////////////
+
+
+struct list_elem *thread_elem;
+for (thread_elem = list_begin(&qeue_timer); 
+		thread_elem != list_end(&qeue_timer); 
+			thread_elem = list_next(thread_elem))
+		{
+			struct timer_thread *t = list_entry(thread_elem, struct timer_thread, thread_element);
+
+			if(timer_ticks() >= t->thread_ticks){
+
+				sema_up(&t->sema);
+				list_pop_front(&qeue_timer);
+			}
+		}
+		
+/////////////////////////////////////
+
+}
+
+/* Sleeps for approximately TICKS timer ticks.  Interrupts must
+	be turned on. */
+void timer_sleep(int64_t ticks)
+{
+
+	int64_t start = timer_ticks();
+	ASSERT(intr_get_level() == INTR_ON);
+	while (timer_elapsed(start) < ticks) thread_yield();
+}
+
+/* Sleeps for approximately MS milliseconds.  Interrupts must be
+	turned on. */
+void timer_msleep(int64_t ms)
+{
+	real_time_sleep(ms, 1000);
+}
+
+/* Sleeps for approximately US microseconds.  Interrupts must be
+	turned on. */
+void timer_usleep(int64_t us)
+{
+	real_time_sleep(us, 1000 * 1000);
+}
+
+/* Sleeps for approximately NS nanoseconds.  Interrupts must be
+	turned on. */
+void timer_nsleep(int64_t ns)
+{
+	real_time_sleep(ns, 1000 * 1000 * 1000);
+}
+
+/* Busy-waits for approximately MS milliseconds.  Interrupts need
+	not be turned on.
+
+	Busy waiting wastes CPU cycles, and busy waiting with
+	interrupts off for the interval between timer ticks or longer
+	will cause timer ticks to be lost.  Thus, use timer_msleep()
+	instead if interrupts are enabled. */
+void timer_mdelay(int64_t ms)
+{
+	real_time_delay(ms, 1000);
+}
+
+/* Sleeps for approximately US microseconds.  Interrupts need not
+	be turned on.
+
+	Busy waiting wastes CPU cycles, and busy waiting with
+	interrupts off for the interval between timer ticks or longer
+	will cause timer ticks to be lost.  Thus, use timer_usleep()
+	instead if interrupts are enabled. */
+void timer_udelay(int64_t us)
+{
+	real_time_delay(us, 1000 * 1000);
+}
+
+/* Sleeps execution for approximately NS nanoseconds.  Interrupts
+	need not be turned on.
+
+	Busy waiting wastes CPU cycles, and busy waiting with
+	interrupts off for the interval between timer ticks or longer
+	will cause timer ticks to be lost.  Thus, use timer_nsleep()
+	instead if interrupts are enabled.*/
+void timer_ndelay(int64_t ns)
+{
+	real_time_delay(ns, 1000 * 1000 * 1000);
+}
+
+/* Prints timer statistics. */
+void timer_print_stats(void)
+{
+	printf("Timer: %" PRId64 " ticks\n", timer_ticks());
+}
+
+/* Returns true if LOOPS iterations waits for more than one timer
+	tick, otherwise false. */
+static bool too_many_loops(unsigned loops)
+{
+	/* Wait for a timer tick. */
+	int64_t start = ticks;
+	while (ticks == start) barrier();
+
+	/* Run LOOPS loops. */
+	start = ticks;
+	busy_wait(loops);
+
+	/* If the tick count changed, we iterated too long. */
+	barrier();
+	return start != ticks;
+}
+
+/* Iterates through a simple loop LOOPS times, for implementing
+	brief delays.
+
+	Marked NO_INLINE because code alignment can significantly
+	affect timings, so that if this function was inlined
+	differently in different places the results would be difficult
+	to predict. */
+static void NO_INLINE busy_wait(int64_t loops)
+{
+	while (loops-- > 0) barrier();
+}
+
+/* Sleep for approximately NUM/DENOM seconds. */
+static void real_time_sleep(int64_t num, int32_t denom)
+{
+	/* Convert NUM/DENOM seconds into timer ticks, rounding down.
+
+			(NUM / DENOM) s
+		---------------------- = NUM * TIMER_FREQ / DENOM ticks.
+		1 s / TIMER_FREQ ticks
+	*/
+	int64_t ticks = num * TIMER_FREQ / denom;
+
+	ASSERT(intr_get_level() == INTR_ON);
+	if (ticks > 0) {
+		/* We're waiting for at least one full timer tick.  Use
+			timer_sleep() because it will yield the CPU to other
+			processes. */
+		timer_sleep(ticks);
+	}
+	else {
+		/* Otherwise, use a busy-wait loop for more accurate
+			sub-tick timing. */
+		real_time_delay(num, denom);
+	}
+}
+
+/* Busy-wait for approximately NUM/DENOM seconds. */
+static void real_time_delay(int64_t num, int32_t denom)
+{
+	/* Scale the numerator and denominator down by 1000 to avoid
+		the possibility of overflow. */
+	ASSERT(denom % 1000 == 0);
+	busy_wait(loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000));
+}
+
diff --git a/devices/timer.h b/devices/timer.h
new file mode 100644
index 0000000000000000000000000000000000000000..ccdd558064f7899cc67a70d5acffa903e660ecd0
--- /dev/null
+++ b/devices/timer.h
@@ -0,0 +1,37 @@
+#ifndef DEVICES_TIMER_H
+#define DEVICES_TIMER_H
+
+#include <round.h>
+#include <stdint.h>
+
+#include <stdbool.h>
+#include "lib/kernel/list.h"
+static struct list qeue_timer;
+
+
+/* Number of timer interrupts per second. */
+// #define TIMER_FREQ 100
+
+void timer_init(const uint16_t timer_freq);
+void timer_calibrate(void);
+
+int64_t timer_ticks(void);
+int64_t timer_elapsed(int64_t);
+
+/* Sleep and yield the CPU to other threads. */
+void timer_sleep(int64_t ticks);
+void timer_msleep(int64_t milliseconds);
+void timer_usleep(int64_t microseconds);
+void timer_nsleep(int64_t nanoseconds);
+
+/* Busy waits. */
+void timer_mdelay(int64_t milliseconds);
+void timer_udelay(int64_t microseconds);
+void timer_ndelay(int64_t nanoseconds);
+
+void timer_print_stats(void);
+
+bool less(const struct list_elem *a, const struct list_elem *b, void *aux );
+
+#endif /* devices/timer.h */
+
diff --git a/devices/vga.c b/devices/vga.c
new file mode 100644
index 0000000000000000000000000000000000000000..a4f7f212f2748de99abefdb69a4b7287999d8efe
--- /dev/null
+++ b/devices/vga.c
@@ -0,0 +1,162 @@
+#include "devices/vga.h"
+
+#include "devices/speaker.h"
+#include "threads/interrupt.h"
+#include "threads/io.h"
+#include "threads/vaddr.h"
+
+#include <round.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+/* VGA text screen support.  See [FREEVGA] for more information. */
+
+/* Number of columns and rows on the text display. */
+#define COL_CNT 80
+#define ROW_CNT 25
+
+/* Current cursor position.  (0,0) is in the upper left corner of
+	the display. */
+static size_t cx, cy;
+
+/* Attribute value for gray text on a black background. */
+#define GRAY_ON_BLACK 0x07
+
+/* Framebuffer.  See [FREEVGA] under "VGA Text Mode Operation".
+	The character at (x,y) is fb[y][x][0].
+	The attribute at (x,y) is fb[y][x][1]. */
+static uint8_t (*fb)[COL_CNT][2];
+
+static void clear_row(size_t y);
+static void cls(void);
+static void newline(void);
+static void move_cursor(void);
+static void find_cursor(size_t* x, size_t* y);
+
+/* Initializes the VGA text display. */
+static void init(void)
+{
+	/* Already initialized? */
+	static bool inited;
+	if (!inited) {
+		fb = ptov(0xb8000);
+		find_cursor(&cx, &cy);
+		inited = true;
+	}
+}
+
+/* Writes C to the VGA text display, interpreting control
+	characters in the conventional ways.  */
+void vga_putc(int c)
+{
+	/* Disable interrupts to lock out interrupt handlers
+		that might write to the console. */
+	enum intr_level old_level = intr_disable();
+
+	init();
+
+	switch (c) {
+		case '\n':
+			newline();
+			break;
+
+		case '\f':
+			cls();
+			break;
+
+		case '\b':
+			if (cx > 0)
+				cx--;
+			break;
+
+		case '\r':
+			cx = 0;
+			break;
+
+		case '\t':
+			cx = ROUND_UP(cx + 1, 8);
+			if (cx >= COL_CNT)
+				newline();
+			break;
+
+		case '\a':
+			intr_set_level(old_level);
+			speaker_beep();
+			intr_disable();
+			break;
+
+		default:
+			fb[cy][cx][0] = c;
+			fb[cy][cx][1] = GRAY_ON_BLACK;
+			if (++cx >= COL_CNT)
+				newline();
+			break;
+	}
+
+	/* Update cursor position. */
+	move_cursor();
+
+	intr_set_level(old_level);
+}
+
+/* Clears the screen and moves the cursor to the upper left. */
+static void cls(void)
+{
+	size_t y;
+
+	for (y = 0; y < ROW_CNT; y++) clear_row(y);
+
+	cx = cy = 0;
+	move_cursor();
+}
+
+/* Clears row Y to spaces. */
+static void clear_row(size_t y)
+{
+	size_t x;
+
+	for (x = 0; x < COL_CNT; x++) {
+		fb[y][x][0] = ' ';
+		fb[y][x][1] = GRAY_ON_BLACK;
+	}
+}
+
+/* Advances the cursor to the first column in the next line on
+	the screen.  If the cursor is already on the last line on the
+	screen, scrolls the screen upward one line. */
+static void newline(void)
+{
+	cx = 0;
+	cy++;
+	if (cy >= ROW_CNT) {
+		cy = ROW_CNT - 1;
+		memmove(&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1));
+		clear_row(ROW_CNT - 1);
+	}
+}
+
+/* Moves the hardware cursor to (cx,cy). */
+static void move_cursor(void)
+{
+	/* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
+	uint16_t cp = cx + COL_CNT * cy;
+	outw(0x3d4, 0x0e | (cp & 0xff00));
+	outw(0x3d4, 0x0f | (cp << 8));
+}
+
+/* Reads the current hardware cursor position into (*X,*Y). */
+static void find_cursor(size_t* x, size_t* y)
+{
+	/* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
+	uint16_t cp;
+
+	outb(0x3d4, 0x0e);
+	cp = inb(0x3d5) << 8;
+
+	outb(0x3d4, 0x0f);
+	cp |= inb(0x3d5);
+
+	*x = cp % COL_CNT;
+	*y = cp / COL_CNT;
+}
diff --git a/devices/vga.h b/devices/vga.h
new file mode 100644
index 0000000000000000000000000000000000000000..1521ca4a843ac0e82f783f21611e936bdd33cd69
--- /dev/null
+++ b/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/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f7ddfbd62ef9fa27b4d7327ba0c1355fd0ffac7c
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,19 @@
+noop
+cat
+cmp
+cp
+echo
+halt
+hex-dump
+rm
+lineup
+matmult
+recursor
+*.d
+lab1test
+lab4test1
+lab4test2
+lab2test
+printf
+recursor_ng
+
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..90607d4325ff0a02c9ed067a72f52351d11f7e89
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,37 @@
+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 rm \
+	lineup recursor lab1test lab2test lab4test1 lab4test2 \
+	printf recursor_ng noop
+
+# The example files should start to work as intended in the following order: 
+# Should work once the main-stack is correctly setup (Lab 1)
+noop_SRC = noop.c
+
+# Should work once the write() syscall is implemented (Lab 2)
+lab1test_SRC = lab1test.c
+
+# Should work once the basic system call handler is implemented (Lab 2)
+lab2test_SRC = lab2test.c
+printf_SRC = printf.c
+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
+lineup_SRC = lineup.c
+rm_SRC = rm.c
+# Should work once exec() is implemented (Lab 4)
+lab4test1_SRC = lab4test1.c
+lab4test2_SRC = lab4test2.c
+
+# Should work once wait() is implemented (Lab 5)
+recursor_SRC = recursor.c
+recursor_ng_SRC = recursor_ng.c
+
+include $(SRCDIR)/Make.config
+include $(SRCDIR)/Makefile.userprog
diff --git a/examples/cat.c b/examples/cat.c
new file mode 100644
index 0000000000000000000000000000000000000000..122f0b3e2ea32d8ffa2c89e240dadb271d35c989
--- /dev/null
+++ b/examples/cat.c
@@ -0,0 +1,30 @@
+/* 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/examples/cmp.c b/examples/cmp.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a25a6722a3df16431fc50542bc7882b6b05187a
--- /dev/null
+++ b/examples/cmp.c
@@ -0,0 +1,67 @@
+/* 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/examples/cp.c b/examples/cp.c
new file mode 100644
index 0000000000000000000000000000000000000000..80cde12020f61d9b594f34c3c5d6851c11e47fae
--- /dev/null
+++ b/examples/cp.c
@@ -0,0 +1,48 @@
+/* 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/examples/echo.c b/examples/echo.c
new file mode 100644
index 0000000000000000000000000000000000000000..18fbbe048620114df11ab7429716de61e1f4e985
--- /dev/null
+++ b/examples/echo.c
@@ -0,0 +1,12 @@
+#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/examples/halt.c b/examples/halt.c
new file mode 100644
index 0000000000000000000000000000000000000000..19eb469e74256defb0df864f596ed0104c8e2ac4
--- /dev/null
+++ b/examples/halt.c
@@ -0,0 +1,13 @@
+/* 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/examples/hex-dump.c b/examples/hex-dump.c
new file mode 100644
index 0000000000000000000000000000000000000000..bdbe07983ba7e74769af33ce6dc9af51676a6eae
--- /dev/null
+++ b/examples/hex-dump.c
@@ -0,0 +1,31 @@
+/* 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/examples/lab1test.c b/examples/lab1test.c
new file mode 100644
index 0000000000000000000000000000000000000000..cc0fad79f84ac5ba933db555d453988179e49625
--- /dev/null
+++ b/examples/lab1test.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <syscall.h>
+
+
+/* NOTE: Will not work until the WRITE syscall is implemented. */
+
+/* Compile it, and run it from userprog/ with the following command
+	pintos -v -k -T 240 --filesys-size=2 -p ../examples/lab1test -a lab1 -- -f -q run 'lab1 arg1 arg2 arg3'
+
+If lab 1 is correctly implemented the arguments should be printed to the
+console.
+Try it wih different arguments and different number of arguments.
+*/
+
+int main(int argc, char* argv[])
+{
+	for (int i = 0; i < argc; i++) {
+		printf("Parameter: %d: %s \n", i, argv[i]);
+	}
+
+	return EXIT_SUCCESS;
+}
diff --git a/examples/lab2test.c b/examples/lab2test.c
new file mode 100644
index 0000000000000000000000000000000000000000..d5e260e8832a3863e823d81759d1e35565cca80d
--- /dev/null
+++ b/examples/lab2test.c
@@ -0,0 +1,273 @@
+/*
+	Compile it, and run it from userprog/ with the following command
+		pintos -v -k --filesys-size=2 -p ../examples/lab2test -a lab2 -- -f -q run lab2
+
+	Complete test suite for most system calls implemented in lab 2.
+	Written by Patrik Sletmo.
+
+	This program tests the minimum file limit and verifies all
+	returned data.
+
+	Tested requirements:
+	  - write must return number of bytes written
+	  - create must return whether the file was created
+	  - a user program must be able to have 128 files open
+		 at the same time
+	  - opening a file must return a valid file descriptor
+	  - opening a file mulitiple times must return unique
+		 file descriptors
+	  - reading from a closed file must fail
+	  - reading from an invalid file descriptor must fail
+	  - attempting to open missing files must fail
+	  - read must return number of bytes read
+	  - read after write must return written data
+	  - reading from console be implemented
+	  - exit must terminate the user program
+
+	Untested requirements:
+	  - halt must shut down the system
+	  - file descriptors must be closed on process exit
+*/
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+#define FD_TEST_COUNT       128
+#define READ_SIZE               50
+#define READ_CONSOLE_COUNT 10
+
+#define RED   "\x1B[31m"
+#define GRN   "\x1B[32m"
+#define YEL   "\x1B[33m"
+#define BLU   "\x1B[34m"
+#define MAG   "\x1B[35m"
+#define CYN   "\x1B[36m"
+#define WHT   "\x1B[37m"
+#define RESET "\x1B[0m"
+
+#define TITLE(x) printf(WHT x RESET)
+#define ERROR(x, ...)                          \
+   printf(RED "ERR: " x RESET, ##__VA_ARGS__); \
+   halt()
+#define SUCCESS(x) printf(GRN x RESET)
+
+int main(void)
+{
+	char* dummyprint = "Hello, world!\n";
+	char* testdata = "sample file content";
+	bool created;
+	int fd;
+	int bytes_written;
+	int bytes_read;
+	char sbuf[READ_SIZE];
+	
+	TITLE("TEST 1: Printing text\n");
+	bytes_written = write(STDOUT_FILENO, dummyprint, strlen(dummyprint));
+	if (bytes_written < 0 || (size_t) bytes_written != strlen(dummyprint)) {
+		ERROR("Incorrect number of written bytes returned from SYS_WRITE.\n");
+	}
+	else {
+		SUCCESS("TEST 1: Passed\n");
+	}
+
+	TITLE("TEST 2: Creating file\n");
+	created = create("test0", strlen(testdata));
+	if (!created) {
+		ERROR("Could not create file \"test0\", does it already exist?\n");
+	}
+
+	created = create("test0", strlen(testdata));
+	if (created) {
+		ERROR("Succeeded in creating already existing file.\n");
+	}
+
+	SUCCESS("TEST 2: Passed\n");
+
+	TITLE("TEST 3: Opening files\n");
+	int file_descriptors[FD_TEST_COUNT];
+	int i;
+
+	printf("Opening %d files", FD_TEST_COUNT);
+	for (i = 0; i < FD_TEST_COUNT; ++i) {
+		fd = open("test0");
+		if (fd == -1) {
+			printf("\n");
+			ERROR("Failed to open file, iteration %d.\n", i + 1);
+		}
+
+		if (fd == STDIN_FILENO || fd == STDOUT_FILENO) {
+			printf("\n");
+			ERROR("Opened file with invalid file descriptor.\n");
+		}
+
+		int j;
+		for (j = 0; j < i; ++j) {
+			if (file_descriptors[j] == fd) {
+				printf("\n");
+				ERROR("Opened file with reoccuring file descriptor.\n");
+			}
+		}
+
+		file_descriptors[i] = fd;
+		printf(".");
+	}
+
+	printf("\nDone!\n");
+	printf("Closing files");
+
+	for (i = 0; i < FD_TEST_COUNT; ++i) {
+		close(file_descriptors[i]);
+		bytes_read = read(file_descriptors[i], sbuf, READ_SIZE);
+		if (bytes_read != -1) {
+			printf("\n");
+			ERROR("Successfully read from closed file.\n");
+		}
+
+		printf(".");
+	}
+
+	printf("\nDone!\n");
+
+	bytes_read = read(STDOUT_FILENO, sbuf, READ_SIZE);
+	if (bytes_read != -1) {
+		ERROR("Successfully read from missing file descriptor.\n");
+	}
+
+	fd = open("foobar");
+	if (fd != -1) {
+		ERROR("Successfully opened missing file.\n");
+	}
+
+	SUCCESS("TEST 3: Passed\n");
+
+	TITLE("TEST 4: Writing to file\n");
+	fd = open("test0");
+	bytes_written = write(fd, testdata, strlen(testdata));
+	if (bytes_written < 0 || (size_t) bytes_written != strlen(testdata)) {
+		ERROR(
+			 "Failed to write %d bytes to file, wrote %d.\n",
+			 strlen(testdata),
+			 bytes_written);
+	}
+	close(fd);
+
+	SUCCESS("TEST 4: Passed\n");
+
+	TITLE("TEST 5: Reading from file\n");
+	fd = open("test0");
+	bytes_read = read(fd, sbuf, READ_SIZE);
+	if (bytes_read < 0 || (size_t) bytes_read != strlen(testdata)) {
+		ERROR(
+			 "Failed to read %d bytes from file, read %d.\n",
+			 strlen(testdata),
+			 bytes_read);
+	}
+
+	if (memcmp(sbuf, testdata, strlen(testdata)) != 0) {
+		ERROR("Read content does not match what was written to file.\n");
+	}
+	close(fd);
+
+	SUCCESS("TEST 5: Passed\n");
+
+	TITLE("TEST 6: Reading from console\n");
+	printf("Type 10 characters: ");
+	bytes_read = read(STDIN_FILENO, sbuf, READ_CONSOLE_COUNT);
+	printf("\n");
+	if (bytes_read != READ_CONSOLE_COUNT) {
+		ERROR(
+			 "Failed to read %d characters from console, read %d.\n",
+			 READ_CONSOLE_COUNT,
+			 bytes_read);
+	}
+	printf("You have typed: %.*s\n", READ_CONSOLE_COUNT, sbuf);
+
+	SUCCESS("TEST 6: Passed\n");
+
+	TITLE(
+		 "The test suite should now exit. Since SYS_WAIT is not implemented yet, the "
+		 "program should hang. ");
+	TITLE("If it does, it means that all tests were successful.\n");
+	TITLE("If wait() is implemented, the program should exit with status 0.\n");
+
+/////////////////////////////////////////////added//////////////////////
+	//Test tell
+	// TITLE("TEST 7: Tell\n");
+	
+	// fd = open("test0");
+	// struct file* dummyfile = "test0";
+
+	// if (&dummyfile->pos !=tell(fd) ) {
+	// 	ERROR(
+	// 		 "Failed to tell position\n");
+	// }
+	// SUCCESS("TEST 7: Passed\n");
+
+	//Test seek
+	TITLE("TEST 8: Tell and seek\n");
+	unsigned after_pos;
+	unsigned dummy_pos = 10;
+	fd = open("test0");
+	seek(fd,dummy_pos);
+	after_pos = tell(fd);
+	if (after_pos !=dummy_pos) {
+		ERROR(
+			 "Failed to seek position\n");
+	}
+	SUCCESS("TEST 8: Passed\n");
+
+	//Test remove
+	TITLE("TEST 9: Remove\n");
+	create("test1", strlen(testdata));
+	remove("test1");
+	fd = open("test1");
+
+	if (fd !=-1) {
+		ERROR(
+			 "Failed to remove file\n");
+	}
+	SUCCESS("TEST 9: Passed\n");
+
+	//Test filesize
+	TITLE("TEST 10: filesize\n");
+
+	fd = open("test0");
+	//struct file* dummyfile_ = "test0";	
+
+	if (filesize(fd) != strlen(testdata)) {
+		ERROR(
+			 "Failed to get filesize\n");
+	}
+	SUCCESS("TEST 10: Passed\n");
+
+
+	//Test sleep
+	TITLE("TEST 11: sleep\n");
+
+	fd = open("test0");
+	int ms = 1000;
+	sleep(ms);
+
+	// if (sleep(ms)) {
+	// 	ERROR(
+	// 		 "Failed to sleep\n");
+	// }
+
+	SUCCESS("TEST 11: Passed 1 sec \n");
+
+	exit(0);
+	ERROR("ERR: Thread did not exit.\n");
+
+	//Test halt
+	// halt();
+	// ERROR("ERR: did not halt.\n");
+	
+		
+	
+	
+
+	////////////////////////////////////////////////////////////////////
+
+
+}
diff --git a/examples/lab4test1.c b/examples/lab4test1.c
new file mode 100644
index 0000000000000000000000000000000000000000..4851c1c021d6f502bae63a52dd2381d8ba85191d
--- /dev/null
+++ b/examples/lab4test1.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <syscall.h>
+
+/* Compile it, and run it from userprog/ with the following command
+	 pintos -v -k -T 240 --filesys-size=2 -p ../examples/lab4test1 -a lab4 -- -f -q run
+lab4
+
+If lab 4 is correctly implemented this program should call itself in an infinite loop
+and spawn itself as a child process indefinetly.
+The PID of each child process created should be printed to the screen.
+In order to see if your implementation works check if the PID of each new  child
+process is incremented as expected.
+*/
+
+int main(void)
+{
+	int pid = exec("lab4");
+	printf("Child process ID: %d\n", pid);
+
+	return EXIT_SUCCESS;
+}
diff --git a/examples/lab4test2.c b/examples/lab4test2.c
new file mode 100644
index 0000000000000000000000000000000000000000..bee44e48af77925655bcb0a6baeaffcf93d8f43b
--- /dev/null
+++ b/examples/lab4test2.c
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <syscall.h>
+
+/* Compile it, and run it from userprog/ with the following command
+	pintos -v -k -T 240 --filesys-size=2 -p ../examples/lab4test2 -a lab4 -p ../examples/printf -a printf -- -f -q run lab4
+
+if lab 4 is correctly implemented this program should call printf 5 times, and spawn
+a child process for each of these calls.
+In order to see if your implementation works check if the string "You got it, use
+your debugging skills during the labs!" is printed 5 times and if the PID of each
+new child process is incremented as it should.
+*/
+
+int main(void)
+{
+	int pid = -1;
+	for (int i = 0; i < 5; i++) {
+		pid = exec("printf");
+		printf("Child %d process ID: %d\n", i, pid);
+	}
+
+	return EXIT_SUCCESS;
+}
diff --git a/examples/lib/.gitignore b/examples/lib/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a4383358ec72fb4d15f30791cb5265a6e06c5416
--- /dev/null
+++ b/examples/lib/.gitignore
@@ -0,0 +1 @@
+*.d
diff --git a/examples/lib/user/.dummy b/examples/lib/user/.dummy
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/lib/user/.gitignore b/examples/lib/user/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a4383358ec72fb4d15f30791cb5265a6e06c5416
--- /dev/null
+++ b/examples/lib/user/.gitignore
@@ -0,0 +1 @@
+*.d
diff --git a/examples/lineup.c b/examples/lineup.c
new file mode 100644
index 0000000000000000000000000000000000000000..bd96b876621ede91d91c34d4a4097f2639a9b7f5
--- /dev/null
+++ b/examples/lineup.c
@@ -0,0 +1,43 @@
+/* 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/examples/noop.c b/examples/noop.c
new file mode 100644
index 0000000000000000000000000000000000000000..8bc5ef79e769e00ab0fa2e42dbbad32271349cd5
--- /dev/null
+++ b/examples/noop.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+#include <syscall.h>
+
+/*  A program that does nothing, a classic no-op.
+ *
+ * Compile it, and run it from userprog/ with the following command:
+	 pintos --filesys-size=2 -p ../examples/noop -a binary -- -f -q run 'binary -s 17'
+*/
+
+int main(int argc, char** argv)
+{
+	(void)argc;
+	(void)argv;
+
+	return EXIT_SUCCESS;
+}
diff --git a/examples/printf.c b/examples/printf.c
new file mode 100644
index 0000000000000000000000000000000000000000..38a5757ef13228d0c629574da9796668cbd209cf
--- /dev/null
+++ b/examples/printf.c
@@ -0,0 +1,9 @@
+#include <stdio.h>
+#include <syscall.h>
+
+int main(void)
+{
+	printf("You got it, use your debugging skills during the labs!\n");
+
+	return EXIT_SUCCESS;
+}
diff --git a/examples/recursor.c b/examples/recursor.c
new file mode 100644
index 0000000000000000000000000000000000000000..7f9bbf826693c979d40a627238cc4db84bf45e7a
--- /dev/null
+++ b/examples/recursor.c
@@ -0,0 +1,36 @@
+#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/examples/recursor_ng.c b/examples/recursor_ng.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e93b89f5277000fbbc138c9d5832525f5bae60c
--- /dev/null
+++ b/examples/recursor_ng.c
@@ -0,0 +1,55 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+#define MAX_SPAWN 100
+
+int main(int argc, char* argv[])
+{
+	char buffer[128];
+	pid_t pid[MAX_SPAWN];
+	int retval = 0;
+	int i, spawn;
+	int ret = 0;
+
+	if (argc != 4) {
+		printf("usage: recursor_ng <string> <depth> <waitp>\n");
+		exit(1);
+	}
+
+	/* Print args. */
+	// printf ("%s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]);
+	printf("+");
+	/* Execute child and wait for it to finish if requested. */
+	spawn = atoi(argv[2]);
+
+	if (spawn > MAX_SPAWN) {
+		printf("Greater than MAX_SPAWN\n");
+		exit(-1);
+	}
+
+	if (spawn != 0) {
+		for (i = 0; i < spawn; i++) {
+			snprintf(
+				 buffer,
+				 sizeof buffer,
+				 "recursor_ng %s %d %s",
+				 argv[1],
+				 atoi(argv[2]) - 1,
+				 argv[3]);
+			pid[i] = exec(buffer);
+		}
+		if (atoi(argv[3])) {
+			for (i = 0; i < spawn; i++) {
+				retval = wait(pid[i]);
+				if (retval < 0)
+					ret = 1;
+			}
+		}
+
+		/* Done. */
+		if (ret)
+			printf("YOU HAVE FAILED\n");
+	}
+	exit(ret);
+}
diff --git a/examples/rm.c b/examples/rm.c
new file mode 100644
index 0000000000000000000000000000000000000000..fe82d49f6662bc5b49d054496a18cea93098d545
--- /dev/null
+++ b/examples/rm.c
@@ -0,0 +1,19 @@
+/* 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/filesys.dsk b/filesys.dsk
new file mode 100644
index 0000000000000000000000000000000000000000..97b592ac0036f787193e44b6c753d0487640e0fa
Binary files /dev/null and b/filesys.dsk differ
diff --git a/filesys/.gitignore b/filesys/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6d5357c015ab6f0b7ee7074381afdd3da82e06eb
--- /dev/null
+++ b/filesys/.gitignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/filesys/Make.vars b/filesys/Make.vars
new file mode 100644
index 0000000000000000000000000000000000000000..b3aa0059bd15f7245ae14abf9eb4cc56bc931c87
--- /dev/null
+++ b/filesys/Make.vars
@@ -0,0 +1,13 @@
+# -*- makefile -*-
+
+kernel.bin: DEFINES = -DUSERPROG -DFILESYS
+KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
+TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended
+GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.no-vm
+SIMULATOR = --qemu
+
+# Uncomment the lines below to enable VM.
+#kernel.bin: DEFINES += -DVM
+#KERNEL_SUBDIRS += vm
+#TEST_SUBDIRS += tests/vm
+#GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.with-vm
diff --git a/filesys/Makefile b/filesys/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..34c10aa4f508714da040e81389fa51e56ba2d97a
--- /dev/null
+++ b/filesys/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel
diff --git a/filesys/directory.c b/filesys/directory.c
new file mode 100644
index 0000000000000000000000000000000000000000..948a9b650d56b700c79a42980bc6eb9bb4dc54f8
--- /dev/null
+++ b/filesys/directory.c
@@ -0,0 +1,218 @@
+#include "filesys/directory.h"
+
+#include "filesys/filesys.h"
+#include "filesys/inode.h"
+#include "threads/malloc.h"
+
+#include <list.h>
+#include <stdio.h>
+#include <string.h>
+
+/* A directory. */
+struct dir {
+	struct inode* inode; /* Backing store. */
+	off_t pos;				/* Current position. */
+};
+
+/* A single directory entry. */
+struct dir_entry {
+	block_sector_t inode_sector; /* Sector number of header. */
+	char name[NAME_MAX + 1];	  /* Null terminated file name. */
+	bool in_use;					  /* In use or free? */
+};
+
+/* Creates a directory with space for ENTRY_CNT entries in the
+	given SECTOR.  Returns true if successful, false on failure. */
+bool dir_create(block_sector_t sector, size_t entry_cnt)
+{
+	return inode_create(sector, entry_cnt * sizeof(struct dir_entry));
+}
+
+/* Opens and returns the directory for the given INODE, of which
+	it takes ownership.  Returns a null pointer on failure. */
+struct dir* dir_open(struct inode* inode)
+{
+	struct dir* dir = calloc(1, sizeof *dir);
+	if (inode != NULL && dir != NULL) {
+		dir->inode = inode;
+		dir->pos = 0;
+		return dir;
+	}
+	else {
+		inode_close(inode);
+		free(dir);
+		return NULL;
+	}
+}
+
+/* Opens the root directory and returns a directory for it.
+	Return true if successful, false on failure. */
+struct dir* dir_open_root(void)
+{
+	return dir_open(inode_open(ROOT_DIR_SECTOR));
+}
+
+/* Opens and returns a new directory for the same inode as DIR.
+	Returns a null pointer on failure. */
+struct dir* dir_reopen(struct dir* dir)
+{
+	return dir_open(inode_reopen(dir->inode));
+}
+
+/* Destroys DIR and frees associated resources. */
+void dir_close(struct dir* dir)
+{
+	if (dir != NULL) {
+		inode_close(dir->inode);
+		free(dir);
+	}
+}
+
+/* Returns the inode encapsulated by DIR. */
+struct inode* dir_get_inode(struct dir* dir)
+{
+	return dir->inode;
+}
+
+/* Searches DIR for a file with the given NAME.
+	If successful, returns true, sets *EP to the directory entry
+	if EP is non-null, and sets *OFSP to the byte offset of the
+	directory entry if OFSP is non-null.
+	otherwise, returns false and ignores EP and OFSP. */
+static bool
+	 lookup(const struct dir* dir, const char* name, struct dir_entry* ep, off_t* ofsp)
+{
+	struct dir_entry e;
+	size_t ofs;
+
+	ASSERT(dir != NULL);
+	ASSERT(name != NULL);
+
+	for (ofs = 0; inode_read_at(dir->inode, &e, sizeof e, ofs) == sizeof e;
+		  ofs += sizeof e)
+		if (e.in_use && !strcmp(name, e.name)) {
+			if (ep != NULL)
+				*ep = e;
+			if (ofsp != NULL)
+				*ofsp = ofs;
+			return true;
+		}
+	return false;
+}
+
+/* Searches DIR for a file with the given NAME
+	and returns true if one exists, false otherwise.
+	On success, sets *INODE to an inode for the file, otherwise to
+	a null pointer.  The caller must close *INODE. */
+bool dir_lookup(const struct dir* dir, const char* name, struct inode** inode)
+{
+	struct dir_entry e;
+
+	ASSERT(dir != NULL);
+	ASSERT(name != NULL);
+
+	if (lookup(dir, name, &e, NULL))
+		*inode = inode_open(e.inode_sector);
+	else
+		*inode = NULL;
+
+	return *inode != NULL;
+}
+
+/* Adds a file named NAME to DIR, which must not already contain a
+	file by that name.  The file's inode is in sector
+	INODE_SECTOR.
+	Returns true if successful, false on failure.
+	Fails if NAME is invalid (i.e. too long) or a disk or memory
+	error occurs. */
+bool dir_add(struct dir* dir, const char* name, block_sector_t inode_sector)
+{
+	struct dir_entry e;
+	off_t ofs;
+	bool success = false;
+
+	ASSERT(dir != NULL);
+	ASSERT(name != NULL);
+
+	/* Check NAME for validity. */
+	if (*name == '\0' || strlen(name) > NAME_MAX)
+		return false;
+
+	/* Check that NAME is not in use. */
+	if (lookup(dir, name, NULL, NULL))
+		goto done;
+
+	/* Set OFS to offset of free slot.
+		If there are no free slots, then it will be set to the
+		current end-of-file.
+
+		inode_read_at() will only return a short read at end of file.
+		Otherwise, we'd need to verify that we didn't get a short
+		read due to something intermittent such as low memory. */
+	for (ofs = 0; inode_read_at(dir->inode, &e, sizeof e, ofs) == sizeof e;
+		  ofs += sizeof e)
+		if (!e.in_use)
+			break;
+
+	/* Write slot. */
+	e.in_use = true;
+	strlcpy(e.name, name, sizeof e.name);
+	e.inode_sector = inode_sector;
+	success = inode_write_at(dir->inode, &e, sizeof e, ofs) == sizeof e;
+
+done:
+	return success;
+}
+
+/* Removes any entry for NAME in DIR.
+	Returns true if successful, false on failure,
+	which occurs only if there is no file with the given NAME. */
+bool dir_remove(struct dir* dir, const char* name)
+{
+	struct dir_entry e;
+	struct inode* inode = NULL;
+	bool success = false;
+	off_t ofs;
+
+	ASSERT(dir != NULL);
+	ASSERT(name != NULL);
+
+	/* Find directory entry. */
+	if (!lookup(dir, name, &e, &ofs))
+		goto done;
+
+	/* Open inode. */
+	inode = inode_open(e.inode_sector);
+	if (inode == NULL)
+		goto done;
+
+	/* Erase directory entry. */
+	e.in_use = false;
+	if (inode_write_at(dir->inode, &e, sizeof e, ofs) != sizeof e)
+		goto done;
+
+	/* Remove inode. */
+	inode_remove(inode);
+	success = true;
+
+done:
+	inode_close(inode);
+	return success;
+}
+
+/* Reads the next directory entry in DIR and stores the name in
+	NAME.  Returns true if successful, false if the directory
+	contains no more entries. */
+bool dir_readdir(struct dir* dir, char name[NAME_MAX + 1])
+{
+	struct dir_entry e;
+
+	while (inode_read_at(dir->inode, &e, sizeof e, dir->pos) == sizeof e) {
+		dir->pos += sizeof e;
+		if (e.in_use) {
+			strlcpy(name, e.name, NAME_MAX + 1);
+			return true;
+		}
+	}
+	return false;
+}
diff --git a/filesys/directory.h b/filesys/directory.h
new file mode 100644
index 0000000000000000000000000000000000000000..e9f58d5f0fdc742a1b03af398dbb66d5e66049f9
--- /dev/null
+++ b/filesys/directory.h
@@ -0,0 +1,31 @@
+#ifndef FILESYS_DIRECTORY_H
+#define FILESYS_DIRECTORY_H
+
+#include "devices/block.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* Maximum length of a file name component.
+	This is the traditional UNIX maximum length.
+	After directories are implemented, this maximum length may be
+	retained, but much longer full path names must be allowed. */
+#define NAME_MAX 14
+
+struct inode;
+
+/* Opening and closing directories. */
+bool dir_create(block_sector_t sector, size_t entry_cnt);
+struct dir* dir_open(struct inode*);
+struct dir* dir_open_root(void);
+struct dir* dir_reopen(struct dir*);
+void dir_close(struct dir*);
+struct inode* dir_get_inode(struct dir*);
+
+/* Reading and writing. */
+bool dir_lookup(const struct dir*, const char* name, struct inode**);
+bool dir_add(struct dir*, const char* name, block_sector_t);
+bool dir_remove(struct dir*, const char* name);
+bool dir_readdir(struct dir*, char name[NAME_MAX + 1]);
+
+#endif /* filesys/directory.h */
diff --git a/filesys/file.c b/filesys/file.c
new file mode 100644
index 0000000000000000000000000000000000000000..fcb1c4336fd2303206c623aa16ae4190ec7a4e78
--- /dev/null
+++ b/filesys/file.c
@@ -0,0 +1,124 @@
+#include "filesys/file.h"
+
+#include "filesys/inode.h"
+#include "threads/malloc.h"
+
+#include <debug.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/filesys/file.h b/filesys/file.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7f1978b8b47fc6d02e3e50e5ef22edfa6d1ea14
--- /dev/null
+++ b/filesys/file.h
@@ -0,0 +1,25 @@
+#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/filesys/filesys.c b/filesys/filesys.c
new file mode 100644
index 0000000000000000000000000000000000000000..cc38f449e420700b18b5d586ab69efa4d1dbf0f4
--- /dev/null
+++ b/filesys/filesys.c
@@ -0,0 +1,99 @@
+#include "filesys/filesys.h"
+
+#include "filesys/directory.h"
+#include "filesys/file.h"
+#include "filesys/free-map.h"
+#include "filesys/inode.h"
+
+#include <debug.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Partition that contains the file system. */
+struct block* fs_device;
+
+static void do_format(void);
+
+/* Initializes the file system module.
+	If FORMAT is true, reformats the file system. */
+void filesys_init(bool format)
+{
+	fs_device = block_get_role(BLOCK_FILESYS);
+	if (fs_device == NULL)
+		PANIC("No file system device found, can't initialize file system.");
+
+	inode_init();
+	free_map_init();
+
+	if (format)
+		do_format();
+
+	free_map_open();
+}
+
+/* Shuts down the file system module, writing any unwritten data
+	to disk. */
+void filesys_done(void)
+{
+	free_map_close();
+}
+
+/* Creates a file named NAME with the given INITIAL_SIZE.
+	Returns true if successful, false otherwise.
+	Fails if a file named NAME already exists,
+	or if internal memory allocation fails. */
+bool filesys_create(const char* name, off_t initial_size)
+{
+	block_sector_t inode_sector = 0;
+	struct dir* dir = dir_open_root();
+	bool success
+		 = (dir != NULL && free_map_allocate(1, &inode_sector)
+			 && inode_create(inode_sector, initial_size)
+			 && dir_add(dir, name, inode_sector));
+	if (!success && inode_sector != 0)
+		free_map_release(inode_sector, 1);
+	dir_close(dir);
+
+	return success;
+}
+
+/* Opens the file with the given NAME.
+	Returns the new file if successful or a null pointer
+	otherwise.
+	Fails if no file named NAME exists,
+	or if an internal memory allocation fails. */
+struct file* filesys_open(const char* name)
+{
+	struct dir* dir = dir_open_root();
+	struct inode* inode = NULL;
+
+	if (dir != NULL)
+		dir_lookup(dir, name, &inode);
+	dir_close(dir);
+
+	return file_open(inode);
+}
+
+/* Deletes the file named NAME.
+	Returns true if successful, false on failure.
+	Fails if no file named NAME exists,
+	or if an internal memory allocation fails. */
+bool filesys_remove(const char* name)
+{
+	struct dir* dir = dir_open_root();
+	bool success = dir != NULL && dir_remove(dir, name);
+	dir_close(dir);
+
+	return success;
+}
+
+/* Formats the file system. */
+static void do_format(void)
+{
+	printf("Formatting file system...");
+	free_map_create();
+	if (!dir_create(ROOT_DIR_SECTOR, 16))
+		PANIC("root directory creation failed");
+	free_map_close();
+	printf("done.\n");
+}
diff --git a/filesys/filesys.h b/filesys/filesys.h
new file mode 100644
index 0000000000000000000000000000000000000000..f580198e6f887d7e471eee3477c419f2e42389e5
--- /dev/null
+++ b/filesys/filesys.h
@@ -0,0 +1,21 @@
+#ifndef FILESYS_FILESYS_H
+#define FILESYS_FILESYS_H
+
+#include "filesys/off_t.h"
+
+#include <stdbool.h>
+
+/* Sectors of system file inodes. */
+#define FREE_MAP_SECTOR 0 /* Free map file inode sector. */
+#define ROOT_DIR_SECTOR 1 /* Root directory file inode sector. */
+
+/* Block device that contains the file system. */
+extern struct block* fs_device;
+
+void filesys_init(bool format);
+void filesys_done(void);
+bool filesys_create(const char* name, off_t initial_size);
+struct file* filesys_open(const char* name);
+bool filesys_remove(const char* name);
+
+#endif /* filesys/filesys.h */
diff --git a/filesys/free-map.c b/filesys/free-map.c
new file mode 100644
index 0000000000000000000000000000000000000000..c8c91f9f67029e740f233524a0286df50b773d8e
--- /dev/null
+++ b/filesys/free-map.c
@@ -0,0 +1,92 @@
+#include "filesys/free-map.h"
+
+#include "filesys/file.h"
+#include "filesys/filesys.h"
+#include "filesys/inode.h"
+
+#include <bitmap.h>
+#include <debug.h>
+#include "threads/synch.h"
+
+
+static struct file* free_map_file; /* Free map file. */
+static struct bitmap* free_map;	  /* Free map, one bit per sector. */
+struct semaphore sema_map;
+
+/* Initializes the free map. */
+void free_map_init(void)
+{
+	sema_init(&sema_map,1);
+	free_map = bitmap_create(block_size(fs_device));
+	if (free_map == NULL)
+		PANIC("bitmap creation failed--file system device is too large");
+	bitmap_mark(free_map, FREE_MAP_SECTOR);
+	bitmap_mark(free_map, ROOT_DIR_SECTOR);
+	
+}
+
+/* Allocates CNT consecutive sectors from the free map and stores
+	the first into *SECTORP.
+	Returns true if successful, false if not enough consecutive
+	sectors were available or if the free_map file could not be
+	written. */
+bool free_map_allocate(size_t cnt, block_sector_t* sectorp)
+{
+
+	sema_down(&sema_map);
+
+	block_sector_t sector = bitmap_scan_and_flip(free_map, 0, cnt, false);
+	if (sector != BITMAP_ERROR && free_map_file != NULL
+		 && !bitmap_write(free_map, free_map_file)) {
+		bitmap_set_multiple(free_map, sector, cnt, false);
+		sector = BITMAP_ERROR;
+	}
+	if (sector != BITMAP_ERROR)
+		*sectorp = sector;
+
+	sema_up(&sema_map);
+//	lock_release(&lock_map);
+	return sector != BITMAP_ERROR;
+}
+
+/* Makes CNT sectors starting at SECTOR available for use. */
+void free_map_release(block_sector_t sector, size_t cnt)
+{
+	sema_down(&sema_map);
+	ASSERT(bitmap_all(free_map, sector, cnt));
+	bitmap_set_multiple(free_map, sector, cnt, false);
+	bitmap_write(free_map, free_map_file);
+	sema_up(&sema_map);
+}
+
+/* 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/filesys/free-map.h b/filesys/free-map.h
new file mode 100644
index 0000000000000000000000000000000000000000..b3378bf03d134bf2daa952cae907e58a40f09a5f
--- /dev/null
+++ b/filesys/free-map.h
@@ -0,0 +1,18 @@
+#ifndef FILESYS_FREE_MAP_H
+#define FILESYS_FREE_MAP_H
+
+#include "devices/block.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+void free_map_init(void);
+void free_map_read(void);
+void free_map_create(void);
+void free_map_open(void);
+void free_map_close(void);
+
+bool free_map_allocate(size_t, block_sector_t*);
+void free_map_release(block_sector_t, size_t);
+
+#endif /* filesys/free-map.h */
diff --git a/filesys/fsutil.c b/filesys/fsutil.c
new file mode 100644
index 0000000000000000000000000000000000000000..5c6348291d86562c4d813f05aaef71a81834ff4e
--- /dev/null
+++ b/filesys/fsutil.c
@@ -0,0 +1,211 @@
+#include "filesys/fsutil.h"
+
+#include "filesys/directory.h"
+#include "filesys/file.h"
+#include "filesys/filesys.h"
+#include "threads/malloc.h"
+#include "threads/palloc.h"
+#include "threads/vaddr.h"
+
+#include <debug.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ustar.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);
+	dir_close(dir);
+	printf("End of listing.\n");
+}
+
+/* Prints the contents of file ARGV[1] to the system console as
+	hex and ASCII. */
+void fsutil_cat(char** argv)
+{
+	const char* file_name = argv[1];
+
+	struct file* file;
+	char* buffer;
+
+	printf("Printing '%s' to the console...\n", file_name);
+	file = filesys_open(file_name);
+	if (file == NULL)
+		PANIC("%s: open failed", file_name);
+	buffer = palloc_get_page(PAL_ASSERT);
+	for (;;) {
+		off_t pos = file_tell(file);
+		off_t n = file_read(file, buffer, PGSIZE);
+		if (n == 0)
+			break;
+
+		hex_dump(pos, buffer, n, true);
+	}
+	palloc_free_page(buffer);
+	file_close(file);
+}
+
+/* Deletes file ARGV[1]. */
+void fsutil_rm(char** argv)
+{
+	const char* file_name = argv[1];
+
+	printf("Deleting '%s'...\n", file_name);
+	if (!filesys_remove(file_name))
+		PANIC("%s: delete failed\n", file_name);
+}
+
+/* Extracts a ustar-format tar archive from the scratch block
+	device into the Pintos file system. */
+void fsutil_extract(char** argv UNUSED)
+{
+	static block_sector_t sector = 0;
+
+	struct block* src;
+	void *header, *data;
+
+	/* Allocate buffers. */
+	header = malloc(BLOCK_SECTOR_SIZE);
+	data = malloc(BLOCK_SECTOR_SIZE);
+	if (header == NULL || data == NULL)
+		PANIC("couldn't allocate buffers");
+
+	/* Open source block device. */
+	src = block_get_role(BLOCK_SCRATCH);
+	if (src == NULL)
+		PANIC("couldn't open scratch device");
+
+	printf(
+		 "Extracting ustar archive from scratch device "
+		 "into file system...\n");
+
+	for (;;) {
+		const char* file_name;
+		const char* error;
+		enum ustar_type type;
+		int size;
+
+		/* Read and parse ustar header. */
+		block_read(src, sector++, header);
+		error = ustar_parse_header(header, &file_name, &type, &size);
+		if (error != NULL)
+			PANIC("bad ustar header in sector %" PRDSNu " (%s)", sector - 1, error);
+
+		if (type == USTAR_EOF) {
+			/* End of archive. */
+			break;
+		}
+		else if (type == USTAR_DIRECTORY)
+			printf("ignoring directory %s\n", file_name);
+		else if (type == USTAR_REGULAR) {
+			struct file* dst;
+
+			printf("Putting '%s' into the file system...\n", file_name);
+
+			/* Create destination file. */
+			if (!filesys_create(file_name, size))
+				PANIC("%s: create failed", file_name);
+			dst = filesys_open(file_name);
+			if (dst == NULL)
+				PANIC("%s: open failed", file_name);
+
+			/* Do copy. */
+			while (size > 0) {
+				int chunk_size = (size > BLOCK_SECTOR_SIZE ? BLOCK_SECTOR_SIZE : size);
+				block_read(src, sector++, data);
+				if (file_write(dst, data, chunk_size) != chunk_size)
+					PANIC("%s: write failed with %d bytes unwritten", file_name, size);
+				size -= chunk_size;
+			}
+
+			/* Finish up. */
+			file_close(dst);
+		}
+	}
+
+	/* Erase the ustar header from the start of the block device,
+		so that the extraction operation is idempotent.  We erase
+		two blocks because two blocks of zeros are the ustar
+		end-of-archive marker. */
+	printf("Erasing ustar archive...\n");
+	memset(header, 0, BLOCK_SECTOR_SIZE);
+	block_write(src, 0, header);
+	block_write(src, 1, header);
+
+	free(data);
+	free(header);
+}
+
+/* Copies file FILE_NAME from the file system to the scratch
+	device, in ustar format.
+
+	The first call to this function will write starting at the
+	beginning of the scratch device.  Later calls advance across
+	the device.  This position is independent of that used for
+	fsutil_extract(), so `extract' should precede all
+	`append's. */
+void fsutil_append(char** argv)
+{
+	static block_sector_t sector = 0;
+
+	const char* file_name = argv[1];
+	void* buffer;
+	struct file* src;
+	struct block* dst;
+	off_t size;
+
+	printf("Appending '%s' to ustar archive on scratch device...\n", file_name);
+
+	/* Allocate buffer. */
+	buffer = malloc(BLOCK_SECTOR_SIZE);
+	if (buffer == NULL)
+		PANIC("couldn't allocate buffer");
+
+	/* Open source file. */
+	src = filesys_open(file_name);
+	if (src == NULL)
+		PANIC("%s: open failed", file_name);
+	size = file_length(src);
+
+	/* Open target block device. */
+	dst = block_get_role(BLOCK_SCRATCH);
+	if (dst == NULL)
+		PANIC("couldn't open scratch device");
+
+	/* Write ustar header to first sector. */
+	if (!ustar_make_header(file_name, USTAR_REGULAR, size, buffer))
+		PANIC("%s: name too long for ustar format", file_name);
+	block_write(dst, sector++, buffer);
+
+	/* Do copy. */
+	while (size > 0) {
+		int chunk_size = size > BLOCK_SECTOR_SIZE ? BLOCK_SECTOR_SIZE : size;
+		if (sector >= block_size(dst))
+			PANIC("%s: out of space on scratch device", file_name);
+		if (file_read(src, buffer, chunk_size) != chunk_size)
+			PANIC("%s: read failed with %" PROTd " bytes unread", file_name, size);
+		memset(buffer + chunk_size, 0, BLOCK_SECTOR_SIZE - chunk_size);
+		block_write(dst, sector++, buffer);
+		size -= chunk_size;
+	}
+
+	/* Write ustar end-of-archive marker, which is two consecutive
+		sectors full of zeros.  Don't advance our position past
+		them, though, in case we have more files to append. */
+	memset(buffer, 0, BLOCK_SECTOR_SIZE);
+	block_write(dst, sector, buffer);
+	block_write(dst, sector + 1, buffer);
+
+	/* Finish up. */
+	file_close(src);
+	free(buffer);
+}
diff --git a/filesys/fsutil.h b/filesys/fsutil.h
new file mode 100644
index 0000000000000000000000000000000000000000..a040d1e33a5c834122de0d552ac63e2443db3a7d
--- /dev/null
+++ b/filesys/fsutil.h
@@ -0,0 +1,10 @@
+#ifndef FILESYS_FSUTIL_H
+#define FILESYS_FSUTIL_H
+
+void fsutil_ls(char** argv);
+void fsutil_cat(char** argv);
+void fsutil_rm(char** argv);
+void fsutil_extract(char** argv);
+void fsutil_append(char** argv);
+
+#endif /* filesys/fsutil.h */
diff --git a/filesys/inode.c b/filesys/inode.c
new file mode 100644
index 0000000000000000000000000000000000000000..233b559895750d64b8ea53caef8de07b3796008c
--- /dev/null
+++ b/filesys/inode.c
@@ -0,0 +1,335 @@
+#include "filesys/inode.h"
+
+#include "filesys/filesys.h"
+#include "filesys/free-map.h"
+#include "threads/malloc.h"
+
+#include <debug.h>
+#include <list.h>
+#include <round.h>
+#include <string.h>
+#include "threads/synch.h"
+
+/* Identifies an inode. */
+#define INODE_MAGIC 0x494e4f44
+
+/* On-disk inode.
+	Must be exactly BLOCK_SECTOR_SIZE bytes long. */
+struct inode_disk {
+	block_sector_t start; /* First data sector. */
+	off_t length;			 /* File size in bytes. */
+	unsigned magic;		 /* Magic number. */
+	uint32_t unused[125]; /* Not used. */
+};
+struct lock lock_inode;
+/* Returns the number of sectors to allocate for an inode SIZE
+	bytes long. */
+static inline size_t bytes_to_sectors(off_t size)
+{
+	return DIV_ROUND_UP(size, BLOCK_SECTOR_SIZE);
+}
+
+/* In-memory inode. */
+struct inode {
+	struct list_elem elem;	 /* Element in inode list. */
+	block_sector_t sector;	 /* Sector number of disk location. */
+	int open_cnt;				 /* Number of openers. */
+	bool removed;				 /* True if deleted, false otherwise. */
+	struct inode_disk data;  /* Inode content. */
+	struct semaphore first;  // added by us
+	struct semaphore second; // added by us
+	int counter;			 // added by us
+
+};
+
+/* Returns the block device sector that contains byte offset POS
+	within INODE.
+	Returns -1 if INODE does not contain data for a byte at offset
+	POS. */
+static block_sector_t byte_to_sector(const struct inode* inode, off_t pos)
+{
+	ASSERT(inode != NULL);
+	if (pos < inode->data.length)
+		return inode->data.start + pos / BLOCK_SECTOR_SIZE;
+	else
+		return -1;
+}
+
+/* List of open inodes, so that opening a single inode twice
+	returns the same `struct inode'. */
+static struct list open_inodes;
+
+
+
+/* Initializes the inode module. */
+void inode_init(void)
+{
+	list_init(&open_inodes);
+	lock_init(&lock_inode);
+}
+
+/* Initializes an inode with LENGTH bytes of data and
+	writes the new inode to sector SECTOR on the file system
+	device.
+	Returns true if successful.
+	Returns false if memory or disk allocation fails. */
+bool inode_create(block_sector_t sector, off_t length)
+{
+	lock_acquire(&lock_inode);
+	//sema_up(&sema_inode);
+	struct inode_disk* disk_inode = NULL;
+	bool success = false;
+
+	ASSERT(length >= 0);
+
+	/* If this assertion fails, the inode structure is not exactly
+		one sector in size, and you should fix that. */
+	ASSERT(sizeof *disk_inode == BLOCK_SECTOR_SIZE);
+
+	disk_inode = calloc(1, sizeof *disk_inode);
+	if (disk_inode != NULL) {
+		size_t sectors = bytes_to_sectors(length);
+		disk_inode->length = length;
+		disk_inode->magic = INODE_MAGIC;
+		if (free_map_allocate(sectors, &disk_inode->start)) {
+			block_write(fs_device, sector, disk_inode);
+			if (sectors > 0) {
+				static char zeros[BLOCK_SECTOR_SIZE];
+				size_t i;
+
+				for (i = 0; i < sectors; i++)
+					block_write(fs_device, disk_inode->start + i, zeros);
+			}
+			success = true;
+		}
+		free(disk_inode);
+	}
+	lock_release(&lock_inode);
+
+	//sema_down(&sema_inode);
+	return success;
+}
+
+/* Reads an inode from SECTOR
+	and returns a `struct inode' that contains it.
+	Returns a null pointer if memory allocation fails. */
+struct inode* inode_open(block_sector_t sector)
+{
+	lock_acquire(&lock_inode);
+	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) {
+
+			if (inode->removed){
+				lock_release(&lock_inode);
+				return NULL;
+			}
+			inode_reopen(inode);
+			lock_release(&lock_inode);
+			return inode;
+		}
+	}
+
+	/* Allocate memory. */
+	inode = malloc(sizeof *inode);
+	if (inode == NULL){
+		lock_release(&lock_inode);
+		return NULL;
+	}
+	inode->counter = 0;
+	sema_init(&(inode->first),1);
+	sema_init(&(inode->second),1);
+	// sema_init(&(inode->third),1);
+	/* Initialize. */
+	list_push_front(&open_inodes, &inode->elem);
+	inode->sector = sector;
+	inode->open_cnt = 1;
+	inode->removed = false;
+	block_read(fs_device, inode->sector, &inode->data);
+	lock_release(&lock_inode);
+
+	return inode;
+}
+
+/* Reopens and returns INODE. */
+struct inode* inode_reopen(struct inode* inode)
+{
+	if (inode != NULL) 
+		inode->open_cnt++;
+	return inode;
+}
+
+/* Returns INODE's inode number. */
+block_sector_t inode_get_inumber(const struct inode* inode)
+{
+	return inode->sector;
+}
+
+/* Closes INODE and writes it to disk.
+	If this was the last reference to INODE, frees its memory.
+	If INODE was also a removed inode, frees its blocks. */
+void inode_close(struct inode* inode)
+{
+
+	/* Ignore null pointer. */
+	if (inode == NULL)
+		return;
+	lock_acquire(&lock_inode);
+	/* Release resources if this was the last opener. */
+	if (--inode->open_cnt == 0) {
+		/* Remove from inode list and release lock. */
+		list_remove(&inode->elem);
+
+		/* Deallocate blocks if removed. */
+		if (inode->removed) {
+			free_map_release(inode->sector, 1);
+			free_map_release(inode->data.start, bytes_to_sectors(inode->data.length));
+		}
+	
+		free(inode);
+	}
+	lock_release(&lock_inode);
+}
+
+/* Marks INODE to be deleted when it is closed by the last caller who
+	has it open. */
+void inode_remove(struct inode* inode)
+{
+	ASSERT(inode != NULL);
+	inode->removed = true;
+}
+
+/* Reads SIZE bytes from INODE into BUFFER, starting at position OFFSET.
+	Returns the number of bytes actually read, which may be less
+	than SIZE if an error occurs or end of file is reached. */
+off_t inode_read_at(struct inode* inode, void* buffer_, off_t size, off_t offset)
+{
+	uint8_t* buffer = buffer_;
+	off_t bytes_read = 0;
+	uint8_t* bounce = NULL;
+	
+	// sema_down(&(inode->third));
+	sema_down(&(inode->second));
+	inode->counter++;
+	if (inode->counter==1){sema_down(&(inode->first));}
+	// sema_up(&(inode->third));
+	sema_up(&(inode->second));
+
+
+	while (size > 0) {
+		/* Disk sector to read, starting byte offset within sector. */
+		block_sector_t sector_idx = byte_to_sector(inode, offset);
+		int sector_ofs = offset % BLOCK_SECTOR_SIZE;
+
+		/* Bytes left in inode, bytes left in sector, lesser of the two. */
+		off_t inode_left = inode_length(inode) - offset;
+		int sector_left = BLOCK_SECTOR_SIZE - sector_ofs;
+		int min_left = inode_left < sector_left ? inode_left : sector_left;
+
+		/* Number of bytes to actually copy out of this sector. */
+		int chunk_size = size < min_left ? size : min_left;
+		if (chunk_size <= 0)
+			break;
+
+		if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) {
+			/* Read full sector directly into caller's buffer. */
+			block_read(fs_device, sector_idx, buffer + bytes_read);
+		}
+		else {
+			/* Read sector into bounce buffer, then partially copy
+				into caller's buffer. */
+			if (bounce == NULL) {
+				bounce = malloc(BLOCK_SECTOR_SIZE);
+				if (bounce == NULL)
+					break;
+			}
+			block_read(fs_device, sector_idx, bounce);
+			memcpy(buffer + bytes_read, bounce + sector_ofs, chunk_size);
+		}
+
+		/* Advance. */
+		size -= chunk_size;
+		offset += chunk_size;
+		bytes_read += chunk_size;
+	}
+	free(bounce);
+
+	sema_down(&(inode->second));
+	inode->counter--;
+	if(inode->counter==0){sema_up(&(inode->first));}
+	sema_up(&(inode->second));
+	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)
+{
+	// sema_down(&(inode->third));
+	sema_down(&(inode->first));
+	// sema_up(&(inode->third));
+	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. */
+		block_sector_t sector_idx = byte_to_sector(inode, offset);
+		int sector_ofs = offset % BLOCK_SECTOR_SIZE;
+
+		/* Bytes left in inode, bytes left in sector, lesser of the two. */
+		off_t inode_left = inode_length(inode) - offset;
+		int sector_left = BLOCK_SECTOR_SIZE - sector_ofs;
+		int min_left = inode_left < sector_left ? inode_left : sector_left;
+
+		/* Number of bytes to actually write into this sector. */
+		int chunk_size = size < min_left ? size : min_left;
+		if (chunk_size <= 0)
+			break;
+
+		if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) {
+			/* Write full sector directly to disk. */
+			block_write(fs_device, sector_idx, buffer + bytes_written);
+		}
+		else {
+			/* We need a bounce buffer. */
+			if (bounce == NULL) {
+				bounce = malloc(BLOCK_SECTOR_SIZE);
+				if (bounce == NULL)
+					break;
+			}
+
+			/* If the sector contains data before or after the chunk
+				we're writing, then we need to read in the sector
+				first.  Otherwise we start with a sector of all zeros. */
+			if (sector_ofs > 0 || chunk_size < sector_left)
+				block_read(fs_device, sector_idx, bounce);
+			else
+				memset(bounce, 0, BLOCK_SECTOR_SIZE);
+			memcpy(bounce + sector_ofs, buffer + bytes_written, chunk_size);
+			block_write(fs_device, sector_idx, bounce);
+		}
+
+		/* Advance. */
+		size -= chunk_size;
+		offset += chunk_size;
+		bytes_written += chunk_size;
+	}
+	free(bounce);
+	sema_up(&(inode->first));
+	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/filesys/inode.h b/filesys/inode.h
new file mode 100644
index 0000000000000000000000000000000000000000..37ab688b8fd89c0e7e8238eeaec22912081125f2
--- /dev/null
+++ b/filesys/inode.h
@@ -0,0 +1,22 @@
+#ifndef FILESYS_INODE_H
+#define FILESYS_INODE_H
+
+#include "devices/block.h"
+#include "filesys/off_t.h"
+
+#include <stdbool.h>
+
+struct bitmap;
+
+void inode_init(void);
+bool inode_create(block_sector_t, off_t);
+struct inode* inode_open(block_sector_t);
+struct inode* inode_reopen(struct inode*);
+block_sector_t inode_get_inumber(const struct inode*);
+void inode_close(struct inode*);
+void inode_remove(struct inode*);
+off_t inode_read_at(struct inode*, void*, off_t size, off_t offset);
+off_t inode_write_at(struct inode*, const void*, off_t size, off_t offset);
+off_t inode_length(const struct inode*);
+
+#endif /* filesys/inode.h */
diff --git a/filesys/off_t.h b/filesys/off_t.h
new file mode 100644
index 0000000000000000000000000000000000000000..078947b2a68db010d9ac610123cc4051e4430638
--- /dev/null
+++ b/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/lib/arithmetic.c b/lib/arithmetic.c
new file mode 100644
index 0000000000000000000000000000000000000000..2c258679458430aa1b046fd5e7fce47f275fff63
--- /dev/null
+++ b/lib/arithmetic.c
@@ -0,0 +1,170 @@
+#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/lib/ctype.h b/lib/ctype.h
new file mode 100644
index 0000000000000000000000000000000000000000..a80959eb5dedc97529e6d40bc9441a4c96da1245
--- /dev/null
+++ b/lib/ctype.h
@@ -0,0 +1,66 @@
+#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/lib/debug.c b/lib/debug.c
new file mode 100644
index 0000000000000000000000000000000000000000..900871eed0eebaac28b2f5262afc8a4d02534d0f
--- /dev/null
+++ b/lib/debug.c
@@ -0,0 +1,36 @@
+#include <debug.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+// Don't raise a warning about unsafe usage of __builtin_frame_address.
+// We know that we will have a calling function, so calling with id = 1 is safe.
+
+/* Prints the call stack, that is, a list of addresses, one in
+	each of the functions we are nested within.  gdb or addr2line
+	may be applied to kernel.o to translate these into file names,
+	line numbers, and function names.  */
+void debug_backtrace(void)
+{
+	static bool explained;
+	void** frame;
+
+	printf("Call stack: %p", __builtin_return_address(0));
+#pragma GCC diagnostic ignored "-Wframe-address"
+	for (frame = __builtin_frame_address(1);
+		  (uintptr_t) frame >= 0x1000 && frame[0] != NULL;
+		  frame = frame[0])
+#pragma GCC diagnostic pop
+		printf(" %p", frame[1]);
+	printf(".\n");
+
+	if (!explained) {
+		explained = true;
+		printf(
+			 "The `backtrace' program can make call stacks useful.\n"
+			 "Read \"Backtraces\" in the \"Debugging Tools\" chapter\n"
+			 "of the Pintos documentation for more information.\n");
+	}
+}
diff --git a/lib/debug.h b/lib/debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..ae49dddbe4504fe92d812c934e544d7d68817e8a
--- /dev/null
+++ b/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);
+void debug_backtrace_all(void);
+
+#endif
+
+/* This is outside the header guard so that debug.h may be
+	included multiple times with different settings of NDEBUG. */
+#undef ASSERT
+#undef NOT_REACHED
+
+#ifndef NDEBUG
+#define ASSERT(CONDITION)                          \
+	if (CONDITION) {                                \
+	}                                               \
+	else {                                          \
+		PANIC("assertion `%s' failed.", #CONDITION); \
+	}
+#define NOT_REACHED() PANIC("executed an unreachable statement");
+#else
+#define ASSERT(CONDITION) ((void) 0)
+#define NOT_REACHED()	  for (;;)
+#endif /* lib/debug.h */
diff --git a/lib/inttypes.h b/lib/inttypes.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7037255c8d5249b35cd72a47747b12f0789a532
--- /dev/null
+++ b/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/lib/kernel/bitmap.c b/lib/kernel/bitmap.c
new file mode 100644
index 0000000000000000000000000000000000000000..ba76c2f084d4d3962b1999085c18a5cd715f2d22
--- /dev/null
+++ b/lib/kernel/bitmap.c
@@ -0,0 +1,338 @@
+#include "bitmap.h"
+
+#include "threads/malloc.h"
+
+#include <debug.h>
+#include <limits.h>
+#include <round.h>
+#include <stdio.h>
+#ifdef FILESYS
+#include "filesys/file.h"
+#endif
+
+/* Element type.
+
+	This must be an unsigned integer type at least as wide as int.
+
+	Each bit represents one bit in the bitmap.
+	If bit 0 in an element represents bit K in the bitmap,
+	then bit 1 in the element represents bit K+1 in the bitmap,
+	and so on. */
+typedef unsigned long elem_type;
+
+/* Number of bits in an element. */
+#define ELEM_BITS (sizeof(elem_type) * CHAR_BIT)
+
+/* From the outside, a bitmap is an array of bits.  From the
+	inside, it's an array of elem_type (defined above) that
+	simulates an array of bits. */
+struct bitmap {
+	size_t bit_cnt;  /* Number of bits. */
+	elem_type* bits; /* Elements that represent bits. */
+};
+
+/* Returns the index of the element that contains the bit
+	numbered BIT_IDX. */
+static inline size_t elem_idx(size_t bit_idx)
+{
+	return bit_idx / ELEM_BITS;
+}
+
+/* Returns an elem_type where only the bit corresponding to
+	BIT_IDX is turned on. */
+static inline elem_type bit_mask(size_t bit_idx)
+{
+	return (elem_type) 1 << (bit_idx % ELEM_BITS);
+}
+
+/* Returns the number of elements required for BIT_CNT bits. */
+static inline size_t elem_cnt(size_t bit_cnt)
+{
+	return DIV_ROUND_UP(bit_cnt, ELEM_BITS);
+}
+
+/* Returns the number of bytes required for BIT_CNT bits. */
+static inline size_t byte_cnt(size_t bit_cnt)
+{
+	return sizeof(elem_type) * elem_cnt(bit_cnt);
+}
+
+/* Returns a bit mask in which the bits actually used in the last
+	element of B's bits are set to 1 and the rest are set to 0. */
+static inline elem_type last_mask(const struct bitmap* b)
+{
+	int last_bits = b->bit_cnt % ELEM_BITS;
+	return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1;
+}
+
+/* Creation and destruction. */
+
+/* Creates and returns a pointer to a newly allocated bitmap with room for
+	BIT_CNT (or more) bits.  Returns a null pointer if memory allocation fails.
+	The caller is responsible for freeing the bitmap, with bitmap_destroy(),
+	when it is no longer needed. */
+struct bitmap* bitmap_create(size_t bit_cnt)
+{
+	struct bitmap* b = malloc(sizeof *b);
+	if (b != NULL) {
+		b->bit_cnt = bit_cnt;
+		b->bits = malloc(byte_cnt(bit_cnt));
+		if (b->bits != NULL || bit_cnt == 0) {
+			bitmap_set_all(b, false);
+			return b;
+		}
+		free(b);
+	}
+	return NULL;
+}
+
+/* Creates and returns a bitmap with BIT_CNT bits in the
+	BLOCK_SIZE bytes of storage preallocated at BLOCK.
+	BLOCK_SIZE must be at least bitmap_needed_bytes(BIT_CNT). */
+struct bitmap*
+	 bitmap_create_in_buf(size_t bit_cnt, void* block, size_t block_size UNUSED)
+{
+	struct bitmap* b = block;
+
+	ASSERT(block_size >= bitmap_buf_size(bit_cnt));
+
+	b->bit_cnt = bit_cnt;
+	b->bits = (elem_type*) (b + 1);
+	bitmap_set_all(b, false);
+	return b;
+}
+
+/* Returns the number of bytes required to accomodate a bitmap
+	with BIT_CNT bits (for use with bitmap_create_in_buf()). */
+size_t bitmap_buf_size(size_t bit_cnt)
+{
+	return sizeof(struct bitmap) + byte_cnt(bit_cnt);
+}
+
+/* Destroys bitmap B, freeing its storage.
+	Not for use on bitmaps created by bitmap_create_in_buf(). */
+void bitmap_destroy(struct bitmap* b)
+{
+	if (b != NULL) {
+		free(b->bits);
+		free(b);
+	}
+}
+
+/* Bitmap size. */
+
+/* Returns the number of bits in B. */
+size_t bitmap_size(const struct bitmap* b)
+{
+	return b->bit_cnt;
+}
+
+/* Setting and testing single bits. */
+
+/* Atomically sets the bit numbered IDX in B to VALUE. */
+void bitmap_set(struct bitmap* b, size_t idx, bool value)
+{
+	ASSERT(b != NULL);
+	ASSERT(idx < b->bit_cnt);
+	if (value)
+		bitmap_mark(b, idx);
+	else
+		bitmap_reset(b, idx);
+}
+
+/* Atomically sets the bit numbered BIT_IDX in B to true. */
+void bitmap_mark(struct bitmap* b, size_t bit_idx)
+{
+	size_t idx = elem_idx(bit_idx);
+	elem_type mask = bit_mask(bit_idx);
+
+	/* This is equivalent to `b->bits[idx] |= mask' except that it
+		is guaranteed to be atomic on a uniprocessor machine.  See
+		the description of the OR instruction in [IA32-v2b]. */
+	asm("orl %1, %0" : "=m"(b->bits[idx]) : "r"(mask) : "cc");
+}
+
+/* Atomically sets the bit numbered BIT_IDX in B to false. */
+void bitmap_reset(struct bitmap* b, size_t bit_idx)
+{
+	size_t idx = elem_idx(bit_idx);
+	elem_type mask = bit_mask(bit_idx);
+
+	/* This is equivalent to `b->bits[idx] &= ~mask' except that it
+		is guaranteed to be atomic on a uniprocessor machine.  See
+		the description of the AND instruction in [IA32-v2a]. */
+	asm("andl %1, %0" : "=m"(b->bits[idx]) : "r"(~mask) : "cc");
+}
+
+/* Atomically toggles the bit numbered IDX in B;
+	that is, if it is true, makes it false,
+	and if it is false, makes it true. */
+void bitmap_flip(struct bitmap* b, size_t bit_idx)
+{
+	size_t idx = elem_idx(bit_idx);
+	elem_type mask = bit_mask(bit_idx);
+
+	/* This is equivalent to `b->bits[idx] ^= mask' except that it
+		is guaranteed to be atomic on a uniprocessor machine.  See
+		the description of the XOR instruction in [IA32-v2b]. */
+	asm("xorl %1, %0" : "=m"(b->bits[idx]) : "r"(mask) : "cc");
+}
+
+/* Returns the value of the bit numbered IDX in B. */
+bool bitmap_test(const struct bitmap* b, size_t idx)
+{
+	ASSERT(b != NULL);
+	ASSERT(idx < b->bit_cnt);
+	return (b->bits[elem_idx(idx)] & bit_mask(idx)) != 0;
+}
+
+/* Setting and testing multiple bits. */
+
+/* Sets all bits in B to VALUE. */
+void bitmap_set_all(struct bitmap* b, bool value)
+{
+	ASSERT(b != NULL);
+
+	bitmap_set_multiple(b, 0, bitmap_size(b), value);
+}
+
+/* Sets the CNT bits starting at START in B to VALUE. */
+void bitmap_set_multiple(struct bitmap* b, size_t start, size_t cnt, bool value)
+{
+	size_t i;
+
+	ASSERT(b != NULL);
+	ASSERT(start <= b->bit_cnt);
+	ASSERT(start + cnt <= b->bit_cnt);
+
+	for (i = 0; i < cnt; i++) bitmap_set(b, start + i, value);
+}
+
+/* Returns the number of bits in B between START and START + CNT,
+	exclusive, that are set to VALUE. */
+size_t bitmap_count(const struct bitmap* b, size_t start, size_t cnt, bool value)
+{
+	size_t i, value_cnt;
+
+	ASSERT(b != NULL);
+	ASSERT(start <= b->bit_cnt);
+	ASSERT(start + cnt <= b->bit_cnt);
+
+	value_cnt = 0;
+	for (i = 0; i < cnt; i++)
+		if (bitmap_test(b, start + i) == value)
+			value_cnt++;
+	return value_cnt;
+}
+
+/* Returns true if any bits in B between START and START + CNT,
+	exclusive, are set to VALUE, and false otherwise. */
+bool bitmap_contains(const struct bitmap* b, size_t start, size_t cnt, bool value)
+{
+	size_t i;
+
+	ASSERT(b != NULL);
+	ASSERT(start <= b->bit_cnt);
+	ASSERT(start + cnt <= b->bit_cnt);
+
+	for (i = 0; i < cnt; i++)
+		if (bitmap_test(b, start + i) == value)
+			return true;
+	return false;
+}
+
+/* Returns true if any bits in B between START and START + CNT,
+	exclusive, are set to true, and false otherwise.*/
+bool bitmap_any(const struct bitmap* b, size_t start, size_t cnt)
+{
+	return bitmap_contains(b, start, cnt, true);
+}
+
+/* Returns true if no bits in B between START and START + CNT,
+	exclusive, are set to true, and false otherwise.*/
+bool bitmap_none(const struct bitmap* b, size_t start, size_t cnt)
+{
+	return !bitmap_contains(b, start, cnt, true);
+}
+
+/* Returns true if every bit in B between START and START + CNT,
+	exclusive, is set to true, and false otherwise. */
+bool bitmap_all(const struct bitmap* b, size_t start, size_t cnt)
+{
+	return !bitmap_contains(b, start, cnt, false);
+}
+
+/* Finding set or unset bits. */
+
+/* Finds and returns the starting index of the first group of CNT
+	consecutive bits in B at or after START that are all set to
+	VALUE.
+	If there is no such group, returns BITMAP_ERROR. */
+size_t bitmap_scan(const struct bitmap* b, size_t start, size_t cnt, bool value)
+{
+	ASSERT(b != NULL);
+	ASSERT(start <= b->bit_cnt);
+
+	if (cnt <= b->bit_cnt) {
+		size_t last = b->bit_cnt - cnt;
+		size_t i;
+		for (i = start; i <= last; i++)
+			if (!bitmap_contains(b, i, cnt, !value))
+				return i;
+	}
+	return BITMAP_ERROR;
+}
+
+/* Finds the first group of CNT consecutive bits in B at or after
+	START that are all set to VALUE, flips them all to !VALUE,
+	and returns the index of the first bit in the group.
+	If there is no such group, returns BITMAP_ERROR.
+	If CNT is zero, returns 0.
+	Bits are set atomically, but testing bits is not atomic with
+	setting them. */
+size_t bitmap_scan_and_flip(struct bitmap* b, size_t start, size_t cnt, bool value)
+{
+	size_t idx = bitmap_scan(b, start, cnt, value);
+	if (idx != BITMAP_ERROR)
+		bitmap_set_multiple(b, idx, cnt, !value);
+	return idx;
+}
+
+/* File input and output. */
+
+#ifdef FILESYS
+/* Returns the number of bytes needed to store B in a file. */
+size_t bitmap_file_size(const struct bitmap* b)
+{
+	return byte_cnt(b->bit_cnt);
+}
+
+/* Reads B from FILE.  Returns true if successful, false
+	otherwise. */
+bool bitmap_read(struct bitmap* b, struct file* file)
+{
+	bool success = true;
+	if (b->bit_cnt > 0) {
+		off_t size = byte_cnt(b->bit_cnt);
+		success = file_read_at(file, b->bits, size, 0) == size;
+		b->bits[elem_cnt(b->bit_cnt) - 1] &= last_mask(b);
+	}
+	return success;
+}
+
+/* Writes B to FILE.  Return true if successful, false
+	otherwise. */
+bool bitmap_write(const struct bitmap* b, struct file* file)
+{
+	off_t size = byte_cnt(b->bit_cnt);
+	return file_write_at(file, b->bits, size, 0) == size;
+}
+#endif /* FILESYS */
+
+/* Debugging. */
+
+/* Dumps the contents of B to the console as hexadecimal. */
+void bitmap_dump(const struct bitmap* b)
+{
+	hex_dump(0, b->bits, byte_cnt(b->bit_cnt), false);
+}
diff --git a/lib/kernel/bitmap.h b/lib/kernel/bitmap.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e3c57c014fd421f7abd63d6b689a8abea908238
--- /dev/null
+++ b/lib/kernel/bitmap.h
@@ -0,0 +1,51 @@
+#ifndef __LIB_KERNEL_BITMAP_H
+#define __LIB_KERNEL_BITMAP_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.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/lib/kernel/console.c b/lib/kernel/console.c
new file mode 100644
index 0000000000000000000000000000000000000000..636f131099e385824f8e292a57fd98d4aa84110a
--- /dev/null
+++ b/lib/kernel/console.c
@@ -0,0 +1,176 @@
+#include "devices/serial.h"
+#include "devices/vga.h"
+#include "threads/init.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+
+#include <console.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+static void vprintf_helper(char, void*);
+static void putchar_have_lock(uint8_t c);
+
+/* The console lock.
+	Both the vga and serial layers do their own locking, so it's
+	safe to call them at any time.
+	But this lock is useful to prevent simultaneous printf() calls
+	from mixing their output, which looks confusing. */
+static struct lock console_lock;
+
+/* True in ordinary circumstances: we want to use the console
+	lock to avoid mixing output between threads, as explained
+	above.
+
+	False in early boot before the point that locks are functional
+	or the console lock has been initialized, or after a kernel
+	panics.  In the former case, taking the lock would cause an
+	assertion failure, which in turn would cause a panic, turning
+	it into the latter case.  In the latter case, if it is a buggy
+	lock_acquire() implementation that caused the panic, we'll
+	likely just recurse. */
+static bool use_console_lock;
+
+/* It's possible, if you add enough debug output to Pintos, to
+	try to recursively grab console_lock from a single thread.  As
+	a real example, I added a printf() call to palloc_free().
+	Here's a real backtrace that resulted:
+
+	lock_console()
+	vprintf()
+	printf()               - palloc() tries to grab the lock again
+	palloc_free()
+	thread_schedule_tail() - another thread dying as we switch threads
+	schedule()
+	thread_yield()
+	intr_handler()         - timer interrupt
+	intr_set_level()
+	serial_putc()
+	putchar_have_lock()
+	putbuf()
+	sys_write()            - one process writing to the console
+	syscall_handler()
+	intr_handler()
+
+	This kind of thing is very difficult to debug, so we avoid the
+	problem by simulating a recursive lock with a depth
+	counter. */
+static int console_lock_depth;
+
+/* Number of characters written to console. */
+static int64_t write_cnt;
+
+/* Enable console locking. */
+void console_init(void)
+{
+	lock_init(&console_lock);
+	use_console_lock = true;
+}
+
+/* Notifies the console that a kernel panic is underway,
+	which warns it to avoid trying to take the console lock from
+	now on. */
+void console_panic(void)
+{
+	use_console_lock = false;
+}
+
+/* Prints console statistics. */
+void console_print_stats(void)
+{
+	printf("Console: %lld characters output\n", write_cnt);
+}
+
+/* Acquires the console lock. */
+static void acquire_console(void)
+{
+	if (!intr_context() && use_console_lock) {
+		if (lock_held_by_current_thread(&console_lock))
+			console_lock_depth++;
+		else
+			lock_acquire(&console_lock);
+	}
+}
+
+/* Releases the console lock. */
+static void release_console(void)
+{
+	if (!intr_context() && use_console_lock) {
+		if (console_lock_depth > 0)
+			console_lock_depth--;
+		else
+			lock_release(&console_lock);
+	}
+}
+
+/* Returns true if the current thread has the console lock,
+	false otherwise. */
+static bool console_locked_by_current_thread(void)
+{
+	return (
+		 intr_context() || !use_console_lock
+		 || lock_held_by_current_thread(&console_lock));
+}
+
+/* The standard vprintf() function,
+	which is like printf() but uses a va_list.
+	Writes its output to both vga display and serial port. */
+int vprintf(const char* format, va_list args)
+{
+	int char_cnt = 0;
+
+	acquire_console();
+	__vprintf(format, args, vprintf_helper, &char_cnt);
+	release_console();
+
+	return char_cnt;
+}
+
+/* Writes string S to the console, followed by a new-line
+	character. */
+int puts(const char* s)
+{
+	acquire_console();
+	while (*s != '\0') putchar_have_lock(*s++);
+	putchar_have_lock('\n');
+	release_console();
+
+	return 0;
+}
+
+/* Writes the N characters in BUFFER to the console. */
+void putbuf(const char* buffer, size_t n)
+{
+	acquire_console();
+	while (n-- > 0) putchar_have_lock(*buffer++);
+	release_console();
+}
+
+/* Writes C to the vga display and serial port. */
+int putchar(int c)
+{
+	acquire_console();
+	putchar_have_lock(c);
+	release_console();
+
+	return c;
+}
+
+/* Helper function for vprintf(). */
+static void vprintf_helper(char c, void* char_cnt_)
+{
+	int* char_cnt = char_cnt_;
+	(*char_cnt)++;
+	putchar_have_lock(c);
+}
+
+/* Writes C to the vga display and serial port.
+	The caller has already acquired the console lock if
+	appropriate. */
+static void putchar_have_lock(uint8_t c)
+{
+	ASSERT(console_locked_by_current_thread());
+	write_cnt++;
+	serial_putc(c);
+	vga_putc(c);
+}
diff --git a/lib/kernel/console.h b/lib/kernel/console.h
new file mode 100644
index 0000000000000000000000000000000000000000..9fbfcf78fcdd1beda985e9eb2ccb29202615385b
--- /dev/null
+++ b/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/lib/kernel/debug.c b/lib/kernel/debug.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb2023b7c83a6c2edafe8506469764778f60a059
--- /dev/null
+++ b/lib/kernel/debug.c
@@ -0,0 +1,121 @@
+#include "devices/serial.h"
+#include "devices/shutdown.h"
+#include "threads/init.h"
+#include "threads/interrupt.h"
+#include "threads/switch.h"
+#include "threads/thread.h"
+#include "threads/vaddr.h"
+
+#include <console.h>
+#include <debug.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Halts the OS, printing the source file name, line number, and
+	function name, plus a user-specific message. */
+void debug_panic(
+	 const char* file, int line, const char* function, const char* message, ...)
+{
+	static int level;
+	va_list args;
+
+	intr_disable();
+	console_panic();
+
+	level++;
+	if (level == 1) {
+		printf("Kernel PANIC at %s:%d in %s(): ", file, line, function);
+
+		va_start(args, message);
+		vprintf(message, args);
+		printf("\n");
+		va_end(args);
+
+		debug_backtrace();
+	}
+	else if (level == 2)
+		printf("Kernel PANIC recursion at %s:%d in %s().\n", file, line, function);
+	else {
+		/* Don't print anything: that's probably why we recursed. */
+	}
+
+	serial_flush();
+	shutdown();
+	for (;;)
+		;
+}
+
+// Don't raise a warning about unsafe usage of __builtin_frame_address.
+// We know that we will have a calling function, so calling with id = 1 is safe.
+
+/* Print call stack of a thread.
+	The thread may be running, ready, or blocked. */
+static void print_stacktrace(struct thread* t, void* aux UNUSED)
+{
+	void *retaddr = NULL, **frame = NULL;
+	const char* status = "UNKNOWN";
+
+	switch (t->status) {
+		case THREAD_RUNNING:
+			status = "RUNNING";
+			break;
+
+		case THREAD_READY:
+			status = "READY";
+			break;
+
+		case THREAD_BLOCKED:
+			status = "BLOCKED";
+			break;
+
+		default:
+			break;
+	}
+
+	printf("Call stack of thread `%s' (status %s):", t->name, status);
+
+	if (t == thread_current()) {
+#pragma GCC diagnostic ignored "-Wframe-address"
+		frame = __builtin_frame_address(1);
+#pragma GCC diagnostic pop
+		retaddr = __builtin_return_address(0);
+	}
+	else {
+		/* Retrieve the values of the base and instruction pointers
+			as they were saved when this thread called switch_threads. */
+		struct switch_threads_frame* saved_frame;
+
+		saved_frame = (struct switch_threads_frame*) t->stack;
+
+		/* Skip threads if they have been added to the all threads
+			list, but have never been scheduled.
+			We can identify because their `stack' member either points
+			at the top of their kernel stack page, or the
+			switch_threads_frame's 'eip' member points at switch_entry.
+			See also threads.c. */
+		if (t->stack == (uint8_t*) t + PGSIZE || saved_frame->eip == switch_entry) {
+			printf(" thread was never scheduled.\n");
+			return;
+		}
+
+		frame = (void**) saved_frame->ebp;
+		retaddr = (void*) saved_frame->eip;
+	}
+
+	printf(" %p", retaddr);
+	for (; (uintptr_t) frame >= 0x1000 && frame[0] != NULL; frame = frame[0])
+		printf(" %p", frame[1]);
+	printf(".\n");
+}
+
+/* Prints call stack of all threads. */
+void debug_backtrace_all(void)
+{
+	enum intr_level oldlevel = intr_disable();
+
+	thread_foreach(print_stacktrace, 0);
+	intr_set_level(oldlevel);
+}
diff --git a/lib/kernel/hash.c b/lib/kernel/hash.c
new file mode 100644
index 0000000000000000000000000000000000000000..6daad226a59f277e55022927cdf704e7bef9b244
--- /dev/null
+++ b/lib/kernel/hash.c
@@ -0,0 +1,389 @@
+/* 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/lib/kernel/hash.h b/lib/kernel/hash.h
new file mode 100644
index 0000000000000000000000000000000000000000..55d43ddfdf512ee42f3d225e59644966b13ff889
--- /dev/null
+++ b/lib/kernel/hash.h
@@ -0,0 +1,99 @@
+#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 "list.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.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/lib/kernel/list.c b/lib/kernel/list.c
new file mode 100644
index 0000000000000000000000000000000000000000..554ed2fcee9b25210ac6ea107de46b617ca7db14
--- /dev/null
+++ b/lib/kernel/list.c
@@ -0,0 +1,484 @@
+#include "list.h"
+
+#include "../debug.h"
+
+/* Our doubly linked lists have two header elements: the "head"
+	just before the first element and the "tail" just after the
+	last element.  The `prev' link of the front header is null, as
+	is the `next' link of the back header.  Their other two links
+	point toward each other via the interior elements of the list.
+
+	An empty list looks like this:
+
+							 +------+     +------+
+						<---| head |<--->| tail |--->
+							 +------+     +------+
+
+	A list with two elements in it looks like this:
+
+		  +------+     +-------+     +-------+     +------+
+	 <---| head |<--->|   1   |<--->|   2   |<--->| tail |<--->
+		  +------+     +-------+     +-------+     +------+
+
+	The symmetry of this arrangement eliminates lots of special
+	cases in list processing.  For example, take a look at
+	list_remove(): it takes only two pointer assignments and no
+	conditionals.  That's a lot simpler than the code would be
+	without header elements.
+
+	(Because only one of the pointers in each header element is used,
+	we could in fact combine them into a single header element
+	without sacrificing this simplicity.  But using two separate
+	elements allows us to do a little bit of checking on some
+	operations, which can be valuable.) */
+
+static bool is_sorted(
+	 struct list_elem* a, struct list_elem* b, list_less_func* less, void* aux) UNUSED;
+
+/* Returns true if ELEM is a head, false otherwise. */
+static inline bool is_head(struct list_elem* elem)
+{
+	return elem != NULL && elem->prev == NULL && elem->next != NULL;
+}
+
+/* Returns true if ELEM is an interior element,
+	false otherwise. */
+static inline bool is_interior(struct list_elem* elem)
+{
+	return elem != NULL && elem->prev != NULL && elem->next != NULL;
+}
+
+/* Returns true if ELEM is a tail, false otherwise. */
+static inline bool is_tail(struct list_elem* elem)
+{
+	return elem != NULL && elem->prev != NULL && elem->next == NULL;
+}
+
+/* Initializes LIST as an empty list. */
+void list_init(struct list* list)
+{
+	ASSERT(list != NULL);
+	list->head.prev = NULL;
+	list->head.next = &list->tail;
+	list->tail.prev = &list->head;
+	list->tail.next = NULL;
+}
+
+/* Returns the beginning of LIST.  */
+struct list_elem* list_begin(struct list* list)
+{
+	ASSERT(list != NULL);
+	return list->head.next;
+}
+
+/* Returns the element after ELEM in its list.  If ELEM is the
+	last element in its list, returns the list tail.  Results are
+	undefined if ELEM is itself a list tail. */
+struct list_elem* list_next(struct list_elem* elem)
+{
+	ASSERT(is_head(elem) || is_interior(elem));
+	return elem->next;
+}
+
+/* Returns LIST's tail.
+
+	list_end() is often used in iterating through a list from
+	front to back.  See the big comment at the top of list.h for
+	an example. */
+struct list_elem* list_end(struct list* list)
+{
+	ASSERT(list != NULL);
+	return &list->tail;
+}
+
+/* Returns the LIST's reverse beginning, for iterating through
+	LIST in reverse order, from back to front. */
+struct list_elem* list_rbegin(struct list* list)
+{
+	ASSERT(list != NULL);
+	return list->tail.prev;
+}
+
+/* Returns the element before ELEM in its list.  If ELEM is the
+	first element in its list, returns the list head.  Results are
+	undefined if ELEM is itself a list head. */
+struct list_elem* list_prev(struct list_elem* elem)
+{
+	ASSERT(is_interior(elem) || is_tail(elem));
+	return elem->prev;
+}
+
+/* Returns LIST's head.
+
+	list_rend() is often used in iterating through a list in
+	reverse order, from back to front.  Here's typical usage,
+	following the example from the top of list.h:
+
+		for (e = list_rbegin (&foo_list); e != list_rend (&foo_list);
+			  e = list_prev (e))
+		  {
+			 struct foo *f = list_entry (e, struct foo, elem);
+			 ...do something with f...
+		  }
+*/
+struct list_elem* list_rend(struct list* list)
+{
+	ASSERT(list != NULL);
+	return &list->head;
+}
+
+/* Return's LIST's head.
+
+	list_head() can be used for an alternate style of iterating
+	through a list, e.g.:
+
+		e = list_head (&list);
+		while ((e = list_next (e)) != list_end (&list))
+		  {
+			 ...
+		  }
+*/
+struct list_elem* list_head(struct list* list)
+{
+	ASSERT(list != NULL);
+	return &list->head;
+}
+
+/* Return's LIST's tail. */
+struct list_elem* list_tail(struct list* list)
+{
+	ASSERT(list != NULL);
+	return &list->tail;
+}
+
+/* Inserts ELEM just before BEFORE, which may be either an
+	interior element or a tail.  The latter case is equivalent to
+	list_push_back(). */
+void list_insert(struct list_elem* before, struct list_elem* elem)
+{
+	ASSERT(is_interior(before) || is_tail(before));
+	ASSERT(elem != NULL);
+
+	elem->prev = before->prev;
+	elem->next = before;
+	before->prev->next = elem;
+	before->prev = elem;
+}
+
+/* Removes elements FIRST though LAST (exclusive) from their
+	current list, then inserts them just before BEFORE, which may
+	be either an interior element or a tail. */
+void list_splice(
+	 struct list_elem* before, struct list_elem* first, struct list_elem* last)
+{
+	ASSERT(is_interior(before) || is_tail(before));
+	if (first == last)
+		return;
+	last = list_prev(last);
+
+	ASSERT(is_interior(first));
+	ASSERT(is_interior(last));
+
+	/* Cleanly remove FIRST...LAST from its current list. */
+	first->prev->next = last->next;
+	last->next->prev = first->prev;
+
+	/* Splice FIRST...LAST into new list. */
+	first->prev = before->prev;
+	last->next = before;
+	before->prev->next = first;
+	before->prev = last;
+}
+
+/* Inserts ELEM at the beginning of LIST, so that it becomes the
+	front in LIST. */
+void list_push_front(struct list* list, struct list_elem* elem)
+{
+	list_insert(list_begin(list), elem);
+}
+
+/* Inserts ELEM at the end of LIST, so that it becomes the
+	back in LIST. */
+void list_push_back(struct list* list, struct list_elem* elem)
+{
+	list_insert(list_end(list), elem);
+}
+
+/* Removes ELEM from its list and returns the element that
+	followed it.  Undefined behavior if ELEM is not in a list.
+
+	A list element must be treated very carefully after removing
+	it from its list.  Calling list_next() or list_prev() on ELEM
+	will return the item that was previously before or after ELEM,
+	but, e.g., list_prev(list_next(ELEM)) is no longer ELEM!
+
+	The list_remove() return value provides a convenient way to
+	iterate and remove elements from a list:
+
+	for (e = list_begin (&list); e != list_end (&list); e = list_remove (e))
+	  {
+		 ...do something with e...
+	  }
+
+	If you need to free() elements of the list then you need to be
+	more conservative.  Here's an alternate strategy that works
+	even in that case:
+
+	while (!list_empty (&list))
+	  {
+		 struct list_elem *e = list_pop_front (&list);
+		 ...do something with e...
+	  }
+*/
+struct list_elem* list_remove(struct list_elem* elem)
+{
+	ASSERT(is_interior(elem));
+	elem->prev->next = elem->next;
+	elem->next->prev = elem->prev;
+	return elem->next;
+}
+
+/* Removes the front element from LIST and returns it.
+	Undefined behavior if LIST is empty before removal. */
+struct list_elem* list_pop_front(struct list* list)
+{
+	struct list_elem* front = list_front(list);
+	list_remove(front);
+	return front;
+}
+
+/* Removes the back element from LIST and returns it.
+	Undefined behavior if LIST is empty before removal. */
+struct list_elem* list_pop_back(struct list* list)
+{
+	struct list_elem* back = list_back(list);
+	list_remove(back);
+	return back;
+}
+
+/* Returns the front element in LIST.
+	Undefined behavior if LIST is empty. */
+struct list_elem* list_front(struct list* list)
+{
+	ASSERT(!list_empty(list));
+	return list->head.next;
+}
+
+/* Returns the back element in LIST.
+	Undefined behavior if LIST is empty. */
+struct list_elem* list_back(struct list* list)
+{
+	ASSERT(!list_empty(list));
+	return list->tail.prev;
+}
+
+/* Returns the number of elements in LIST.
+	Runs in O(n) in the number of elements. */
+size_t list_size(struct list* list)
+{
+	struct list_elem* e;
+	size_t cnt = 0;
+
+	for (e = list_begin(list); e != list_end(list); e = list_next(e)) cnt++;
+	return cnt;
+}
+
+/* Returns true if LIST is empty, false otherwise. */
+bool list_empty(struct list* list)
+{
+	return list_begin(list) == list_end(list);
+}
+
+/* Swaps the `struct list_elem *'s that A and B point to. */
+static void swap(struct list_elem** a, struct list_elem** b)
+{
+	struct list_elem* t = *a;
+	*a = *b;
+	*b = t;
+}
+
+/* Reverses the order of LIST. */
+void list_reverse(struct list* list)
+{
+	if (!list_empty(list)) {
+		struct list_elem* e;
+
+		for (e = list_begin(list); e != list_end(list); e = e->prev)
+			swap(&e->prev, &e->next);
+		swap(&list->head.next, &list->tail.prev);
+		swap(&list->head.next->prev, &list->tail.prev->next);
+	}
+}
+
+/* Returns true only if the list elements A through B (exclusive)
+	are in order according to LESS given auxiliary data AUX. */
+static bool
+	 is_sorted(struct list_elem* a, struct list_elem* b, list_less_func* less, void* aux)
+{
+	if (a != b)
+		while ((a = list_next(a)) != b)
+			if (less(a, list_prev(a), aux))
+				return false;
+	return true;
+}
+
+/* Finds a run, starting at A and ending not after B, of list
+	elements that are in nondecreasing order according to LESS
+	given auxiliary data AUX.  Returns the (exclusive) end of the
+	run.
+	A through B (exclusive) must form a non-empty range. */
+static struct list_elem* find_end_of_run(
+	 struct list_elem* a, struct list_elem* b, list_less_func* less, void* aux)
+{
+	ASSERT(a != NULL);
+	ASSERT(b != NULL);
+	ASSERT(less != NULL);
+	ASSERT(a != b);
+
+	do {
+		a = list_next(a);
+	} while (a != b && !less(a, list_prev(a), aux));
+	return a;
+}
+
+/* Merges A0 through A1B0 (exclusive) with A1B0 through B1
+	(exclusive) to form a combined range also ending at B1
+	(exclusive).  Both input ranges must be nonempty and sorted in
+	nondecreasing order according to LESS given auxiliary data
+	AUX.  The output range will be sorted the same way. */
+static void inplace_merge(
+	 struct list_elem* a0,
+	 struct list_elem* a1b0,
+	 struct list_elem* b1,
+	 list_less_func* less,
+	 void* aux)
+{
+	ASSERT(a0 != NULL);
+	ASSERT(a1b0 != NULL);
+	ASSERT(b1 != NULL);
+	ASSERT(less != NULL);
+	ASSERT(is_sorted(a0, a1b0, less, aux));
+	ASSERT(is_sorted(a1b0, b1, less, aux));
+
+	while (a0 != a1b0 && a1b0 != b1)
+		if (!less(a1b0, a0, aux))
+			a0 = list_next(a0);
+		else {
+			a1b0 = list_next(a1b0);
+			list_splice(a0, list_prev(a1b0), a1b0);
+		}
+}
+
+/* Sorts LIST according to LESS given auxiliary data AUX, using a
+	natural iterative merge sort that runs in O(n lg n) time and
+	O(1) space in the number of elements in LIST. */
+void list_sort(struct list* list, list_less_func* less, void* aux)
+{
+	size_t output_run_cnt; /* Number of runs output in current pass. */
+
+	ASSERT(list != NULL);
+	ASSERT(less != NULL);
+
+	/* Pass over the list repeatedly, merging adjacent runs of
+		nondecreasing elements, until only one run is left. */
+	do {
+		struct list_elem* a0;	/* Start of first run. */
+		struct list_elem* a1b0; /* End of first run, start of second. */
+		struct list_elem* b1;	/* End of second run. */
+
+		output_run_cnt = 0;
+		for (a0 = list_begin(list); a0 != list_end(list); a0 = b1) {
+			/* Each iteration produces one output run. */
+			output_run_cnt++;
+
+			/* Locate two adjacent runs of nondecreasing elements
+				A0...A1B0 and A1B0...B1. */
+			a1b0 = find_end_of_run(a0, list_end(list), less, aux);
+			if (a1b0 == list_end(list))
+				break;
+			b1 = find_end_of_run(a1b0, list_end(list), less, aux);
+
+			/* Merge the runs. */
+			inplace_merge(a0, a1b0, b1, less, aux);
+		}
+	} while (output_run_cnt > 1);
+
+	ASSERT(is_sorted(list_begin(list), list_end(list), less, aux));
+}
+
+/* Inserts ELEM in the proper position in LIST, which must be
+	sorted according to LESS given auxiliary data AUX.
+	Runs in O(n) average case in the number of elements in LIST. */
+void list_insert_ordered(
+	 struct list* list, struct list_elem* elem, list_less_func* less, void* aux)
+{
+	struct list_elem* e;
+
+	ASSERT(list != NULL);
+	ASSERT(elem != NULL);
+	ASSERT(less != NULL);
+
+	for (e = list_begin(list); e != list_end(list); e = list_next(e))
+		if (less(elem, e, aux))
+			break;
+	return list_insert(e, elem);
+}
+
+/* Iterates through LIST and removes all but the first in each
+	set of adjacent elements that are equal according to LESS
+	given auxiliary data AUX.  If DUPLICATES is non-null, then the
+	elements from LIST are appended to DUPLICATES. */
+void list_unique(
+	 struct list* list, struct list* duplicates, list_less_func* less, void* aux)
+{
+	struct list_elem *elem, *next;
+
+	ASSERT(list != NULL);
+	ASSERT(less != NULL);
+	if (list_empty(list))
+		return;
+
+	elem = list_begin(list);
+	while ((next = list_next(elem)) != list_end(list))
+		if (!less(elem, next, aux) && !less(next, elem, aux)) {
+			list_remove(next);
+			if (duplicates != NULL)
+				list_push_back(duplicates, next);
+		}
+		else
+			elem = next;
+}
+
+/* Returns the element in LIST with the largest value according
+	to LESS given auxiliary data AUX.  If there is more than one
+	maximum, returns the one that appears earlier in the list.  If
+	the list is empty, returns its tail. */
+struct list_elem* list_max(struct list* list, list_less_func* less, void* aux)
+{
+	struct list_elem* max = list_begin(list);
+	if (max != list_end(list)) {
+		struct list_elem* e;
+
+		for (e = list_next(max); e != list_end(list); e = list_next(e))
+			if (less(max, e, aux))
+				max = e;
+	}
+	return max;
+}
+
+/* Returns the element in LIST with the smallest value according
+	to LESS given auxiliary data AUX.  If there is more than one
+	minimum, returns the one that appears earlier in the list.  If
+	the list is empty, returns its tail. */
+struct list_elem* list_min(struct list* list, list_less_func* less, void* aux)
+{
+	struct list_elem* min = list_begin(list);
+	if (min != list_end(list)) {
+		struct list_elem* e;
+
+		for (e = list_next(min); e != list_end(list); e = list_next(e))
+			if (less(e, min, aux))
+				min = e;
+	}
+	return min;
+}
diff --git a/lib/kernel/list.h b/lib/kernel/list.h
new file mode 100644
index 0000000000000000000000000000000000000000..09e212d99fbde2eaf2b68fb6399947fea42a386b
--- /dev/null
+++ b/lib/kernel/list.h
@@ -0,0 +1,179 @@
+#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)))
+
+/* List initialization.
+
+	A list may be initialized by calling list_init():
+
+		 struct list my_list;
+		 list_init (&my_list);
+
+	or with an initializer using LIST_INITIALIZER:
+
+		 struct list my_list = LIST_INITIALIZER (my_list); */
+#define LIST_INITIALIZER(NAME) \
+	{                           \
+		{NULL, &(NAME).tail},    \
+		{                        \
+			&(NAME).head, NULL    \
+		}                        \
+	}
+
+void list_init(struct list*);
+
+/* List traversal. */
+struct list_elem* list_begin(struct list*);
+struct list_elem* list_next(struct list_elem*);
+struct list_elem* list_end(struct list*);
+
+struct list_elem* list_rbegin(struct list*);
+struct list_elem* list_prev(struct list_elem*);
+struct list_elem* list_rend(struct list*);
+
+struct list_elem* list_head(struct list*);
+struct list_elem* list_tail(struct list*);
+
+/* List insertion. */
+void list_insert(struct list_elem*, struct list_elem*);
+void list_splice(
+	 struct list_elem* before, struct list_elem* first, struct list_elem* last);
+void list_push_front(struct list*, struct list_elem*);
+void list_push_back(struct list*, struct list_elem*);
+
+/* List removal. */
+struct list_elem* list_remove(struct list_elem*);
+struct list_elem* list_pop_front(struct list*);
+struct list_elem* list_pop_back(struct list*);
+
+/* List elements. */
+struct list_elem* list_front(struct list*);
+struct list_elem* list_back(struct list*);
+
+/* List properties. */
+size_t list_size(struct list*);
+bool list_empty(struct list*);
+
+/* Miscellaneous. */
+void list_reverse(struct list*);
+
+/* Compares the value of two list elements A and B, given
+	auxiliary data AUX.  Returns true if A is less than B, or
+	false if A is greater than or equal to B. */
+typedef bool
+	 list_less_func(const struct list_elem* a, const struct list_elem* b, void* aux);
+
+/* Operations on lists with ordered elements. */
+void list_sort(struct list*, list_less_func*, void* aux);
+void list_insert_ordered(struct list*, struct list_elem*, list_less_func*, void* aux);
+void list_unique(struct list*, struct list* duplicates, list_less_func*, void* aux);
+
+/* Max and min. */
+struct list_elem* list_max(struct list*, list_less_func*, void* aux);
+struct list_elem* list_min(struct list*, list_less_func*, void* aux);
+
+#endif /* lib/kernel/list.h */
diff --git a/lib/kernel/stdio.h b/lib/kernel/stdio.h
new file mode 100644
index 0000000000000000000000000000000000000000..142895ffccd83b6d43625dc63ddfcd1e3e7b4a7e
--- /dev/null
+++ b/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/lib/limits.h b/lib/limits.h
new file mode 100644
index 0000000000000000000000000000000000000000..61ddc6c3f7da60478192ccda033f4c7e82b8f455
--- /dev/null
+++ b/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/lib/packed.h b/lib/packed.h
new file mode 100644
index 0000000000000000000000000000000000000000..2595498bf713de7007e7a24c0111092b41612f37
--- /dev/null
+++ b/lib/packed.h
@@ -0,0 +1,10 @@
+#ifndef __LIB_PACKED_H
+#define __LIB_PACKED_H
+
+/* The "packed" attribute, when applied to a structure, prevents
+	GCC from inserting padding bytes between or after structure
+	members.  It must be specified at the time of the structure's
+	definition, normally just after the closing brace. */
+#define PACKED __attribute__((packed))
+
+#endif /* lib/packed.h */
diff --git a/lib/random.c b/lib/random.c
new file mode 100644
index 0000000000000000000000000000000000000000..0db03b28301aae55a3b21e9636aa239d77195c16
--- /dev/null
+++ b/lib/random.c
@@ -0,0 +1,81 @@
+#include "random.h"
+
+#include "debug.h"
+
+#include <stdbool.h>
+#include <stdint.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;
+
+	if (inited)
+		return;
+
+	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/lib/random.h b/lib/random.h
new file mode 100644
index 0000000000000000000000000000000000000000..538982c3ca7188d0e16fa6180f885bd2396f5d69
--- /dev/null
+++ b/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/lib/round.h b/lib/round.h
new file mode 100644
index 0000000000000000000000000000000000000000..cbf398929939969d2ca9a3feed8a58837dc747cb
--- /dev/null
+++ b/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/lib/stdarg.h b/lib/stdarg.h
new file mode 100644
index 0000000000000000000000000000000000000000..bc57891e427c01aeb7af720acfd0844e0af2eafc
--- /dev/null
+++ b/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/lib/stdbool.h b/lib/stdbool.h
new file mode 100644
index 0000000000000000000000000000000000000000..fa91b7682c7c7c79d90239cca44d16df0540c730
--- /dev/null
+++ b/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/lib/stddef.h b/lib/stddef.h
new file mode 100644
index 0000000000000000000000000000000000000000..c54bcd24c1da3650be102e984865e5660c532e3e
--- /dev/null
+++ b/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/lib/stdint.h b/lib/stdint.h
new file mode 100644
index 0000000000000000000000000000000000000000..ef5f214e150a59102dc313c6702483b6e26d7a9f
--- /dev/null
+++ b/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/lib/stdio.c b/lib/stdio.c
new file mode 100644
index 0000000000000000000000000000000000000000..72c98d0296501f3dd403f3c2a003ebb76e5db95b
--- /dev/null
+++ b/lib/stdio.c
@@ -0,0 +1,608 @@
+#include <ctype.h>
+#include <inttypes.h>
+#include <round.h>
+#include <stdint.h>
+#include <stdio.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;
+	}
+}
+
+/* Prints SIZE, which represents a number of bytes, in a
+	human-readable format, e.g. "256 kB". */
+void print_human_readable_size(uint64_t size)
+{
+	if (size == 1)
+		printf("1 byte");
+	else {
+		static const char* factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL};
+		const char** fp;
+
+		for (fp = factors; size >= 1024 && fp[1] != NULL; fp++) size /= 1024;
+		printf("%" PRIu64 " %s", size, *fp);
+	}
+}
diff --git a/lib/stdio.h b/lib/stdio.h
new file mode 100644
index 0000000000000000000000000000000000000000..08e282ead5a9d8cbe5e7aae87c2f04d0474f38da
--- /dev/null
+++ b/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);
+void print_human_readable_size(uint64_t sz);
+
+/* Internal functions. */
+void __vprintf(
+	 const char* format, va_list args, void (*output)(char, void*), void* aux);
+void __printf(const char* format, void (*output)(char, void*), void* aux, ...);
+
+/* Try to be helpful. */
+#define sprintf  dont_use_sprintf_use_snprintf
+#define vsprintf dont_use_vsprintf_use_vsnprintf
+
+#endif /* lib/stdio.h */
diff --git a/lib/stdlib.c b/lib/stdlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..36a9618f36b129180170f3942abbb93d15c21255
--- /dev/null
+++ b/lib/stdlib.c
@@ -0,0 +1,208 @@
+#include <ctype.h>
+#include <debug.h>
+#include <random.h>
+#include <stdbool.h>
+#include <stdlib.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/lib/stdlib.h b/lib/stdlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..c361f4b36adec8e39f56a7bee2067b537f5d16a7
--- /dev/null
+++ b/lib/stdlib.h
@@ -0,0 +1,32 @@
+#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/lib/string.c b/lib/string.c
new file mode 100644
index 0000000000000000000000000000000000000000..adffcda6f075a5844071108a9052301aec3d6f1d
--- /dev/null
+++ b/lib/string.c
@@ -0,0 +1,349 @@
+#include <debug.h>
+#include <string.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;
+}
+
+// Disable warnings about comparing nonnull argument to NULL. In this case it's not
+// a problem.
+
+/* 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_;
+
+#pragma GCC diagnostic ignored "-Wnonnull-compare"
+	ASSERT(string != NULL);
+#pragma GCC diagnostic pop
+	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_;
+}
+
+// Disable warnings about comparing nonnull argument to NULL. In this case it's not
+// a problem.
+/* Returns the length of STRING. */
+size_t strlen(const char* string)
+{
+	const char* p;
+
+#pragma GCC diagnostic ignored "-Wnonnull-compare"
+	ASSERT(string != NULL);
+#pragma GCC diagnostic pop
+
+	for (p = string; *p != '\0'; p++) continue;
+	return p - string;
+}
+
+/* If STRING is less than MAXLEN characters in length, returns
+	its actual length.  Otherwise, returns MAXLEN. */
+size_t strnlen(const char* string, size_t maxlen)
+{
+	size_t length;
+
+	for (length = 0; string[length] != '\0' && length < maxlen; length++) continue;
+	return length;
+}
+
+/* Copies string SRC to DST.  If SRC is longer than SIZE - 1
+	characters, only SIZE - 1 characters are copied.  A null
+	terminator is always written to DST, unless SIZE is 0.
+	Returns the length of SRC, not including the null terminator.
+
+	strlcpy() is not in the standard C library, but it is an
+	increasingly popular extension.  See
+	http://www.courtesan.com/todd/papers/strlcpy.html for
+	information on strlcpy(). */
+size_t strlcpy(char* dst, const char* src, size_t size)
+{
+	size_t src_len;
+
+	ASSERT(dst != NULL);
+	ASSERT(src != NULL);
+
+	src_len = strlen(src);
+	if (size > 0) {
+		size_t dst_len = size - 1;
+		if (src_len < dst_len)
+			dst_len = src_len;
+		memcpy(dst, src, dst_len);
+		dst[dst_len] = '\0';
+	}
+	return src_len;
+}
+
+/* Concatenates string SRC to DST.  The concatenated string is
+	limited to SIZE - 1 characters.  A null terminator is always
+	written to DST, unless SIZE is 0.  Returns the length that the
+	concatenated string would have assuming that there was
+	sufficient space, not including a null terminator.
+
+	strlcat() is not in the standard C library, but it is an
+	increasingly popular extension.  See
+	http://www.courtesan.com/todd/papers/strlcpy.html for
+	information on strlcpy(). */
+size_t strlcat(char* dst, const char* src, size_t size)
+{
+	size_t src_len, dst_len;
+
+	ASSERT(dst != NULL);
+	ASSERT(src != NULL);
+
+	src_len = strlen(src);
+	dst_len = strlen(dst);
+	if (size > 0 && dst_len < size) {
+		size_t copy_cnt = size - dst_len - 1;
+		if (src_len < copy_cnt)
+			copy_cnt = src_len;
+		memcpy(dst + dst_len, src, copy_cnt);
+		dst[dst_len + copy_cnt] = '\0';
+	}
+	return src_len + dst_len;
+}
diff --git a/lib/string.h b/lib/string.h
new file mode 100644
index 0000000000000000000000000000000000000000..7c4b3237519f92f86da13746fc7f2c228801f7cd
--- /dev/null
+++ b/lib/string.h
@@ -0,0 +1,35 @@
+#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 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/lib/syscall-nr.h b/lib/syscall-nr.h
new file mode 100644
index 0000000000000000000000000000000000000000..31bf0efaa2d21a8e23c7abdb2f73f9944d216e23
--- /dev/null
+++ b/lib/syscall-nr.h
@@ -0,0 +1,40 @@
+#ifndef __LIB_SYSCALL_NR_H
+#define __LIB_SYSCALL_NR_H
+
+/* System call numbers. */
+enum {
+	/* Projects 2 and later. */
+	SYS_HALT,	  /* Halt the operating system. */
+	SYS_EXIT,	  /* Terminate this process. */
+	SYS_EXEC,	  /* Start another process. */
+	SYS_WAIT,	  /* Wait for a child process to die. */
+	SYS_CREATE,	  /* Create a file. */
+	SYS_REMOVE,	  /* Delete a file. */
+	SYS_OPEN,	  /* Open a file. */
+	SYS_FILESIZE, /* Obtain a file's size. */
+	SYS_READ,	  /* Read from a file. */
+	SYS_WRITE,	  /* Write to a file. */
+	SYS_SEEK,	  /* Change position in a file. */
+	SYS_TELL,	  /* Report current position in a file. */
+	SYS_CLOSE,	  /* Close a file. */
+
+	/* Project 3 and optionally project 4. */
+	SYS_MMAP,	/* Map a file into memory. */
+	SYS_MUNMAP, /* Remove a memory mapping. */
+
+	/* Project 4 only. */
+	SYS_CHDIR,	 /* Change the current directory. */
+	SYS_MKDIR,	 /* Create a directory. */
+	SYS_READDIR, /* Reads a directory entry. */
+	SYS_ISDIR,	 /* Tests if a fd represents a directory. */
+	SYS_INUMBER,	 /* Returns the inode number for a fd. */
+
+
+	////////////////////////////////
+
+	SYS_SLEEP,
+	////////////////////////////////
+	SYS_NUMBER_OF_CALLS /* Needs to be last to be correct */
+};
+
+#endif /* lib/syscall-nr.h */
diff --git a/lib/user/console.c b/lib/user/console.c
new file mode 100644
index 0000000000000000000000000000000000000000..54190178240b8e01fe7f34cc109e4a3884db97c6
--- /dev/null
+++ b/lib/user/console.c
@@ -0,0 +1,86 @@
+#include <stdio.h>
+#include <string.h>
+#include <syscall-nr.h>
+#include <syscall.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/lib/user/debug.c b/lib/user/debug.c
new file mode 100644
index 0000000000000000000000000000000000000000..d5ba3308327579f8b24e2eca4c4869fa59d670a4
--- /dev/null
+++ b/lib/user/debug.c
@@ -0,0 +1,24 @@
+#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/lib/user/entry.c b/lib/user/entry.c
new file mode 100644
index 0000000000000000000000000000000000000000..0723d0150a9664ed952aace4e8defa339960ffc3
--- /dev/null
+++ b/lib/user/entry.c
@@ -0,0 +1,16 @@
+#include <syscall.h>
+#include "threads/vaddr.h"
+#include <stddef.h>
+#include <string.h>
+#include <threads/init.h>
+
+#include <stddef.h>
+#include <string.h>
+/////////////////////////7
+int main(int, char*[]);
+void _start(int argc, char* argv[]);
+
+void _start(int argc, char* argv[])
+{
+	exit(main(argc, argv));
+}
diff --git a/lib/user/stdio.h b/lib/user/stdio.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e888eb462dd84f6b5624598eccc906cd45bd7d5
--- /dev/null
+++ b/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/lib/user/syscall.c b/lib/user/syscall.c
new file mode 100644
index 0000000000000000000000000000000000000000..b0e157bd6a4f494ec8af067aff8ffe74b0b756bb
--- /dev/null
+++ b/lib/user/syscall.c
@@ -0,0 +1,162 @@
+#include "../syscall-nr.h"
+
+#include <syscall.h>
+
+/* Invokes syscall NUMBER, passing no arguments, and returns the
+	return value as an `int'. */
+#define syscall0(NUMBER)                                        \
+	({                                                           \
+		int retval;                                               \
+		asm volatile("pushl %[number]; int $0x30; addl $4, %%esp" \
+						 : "=a"(retval)                               \
+						 : [number] "i"(NUMBER)                       \
+						 : "memory");                                 \
+		retval;                                                   \
+	})
+
+/* Invokes syscall NUMBER, passing argument ARG0, and returns the
+	return value as an `int'. */
+#define syscall1(NUMBER, ARG0)                                                 \
+	({                                                                          \
+		int retval;                                                              \
+		asm volatile("pushl %[arg0]; pushl %[number]; int $0x30; addl $8, %%esp" \
+						 : "=a"(retval)                                              \
+						 : [number] "i"(NUMBER), [arg0] "g"(ARG0)                    \
+						 : "memory");                                                \
+		retval;                                                                  \
+	})
+
+/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and
+	returns the return value as an `int'. */
+#define syscall2(NUMBER, ARG0, ARG1)                                 \
+	({                                                                \
+		int retval;                                                    \
+		asm volatile(                                                  \
+			 "pushl %[arg1]; pushl %[arg0]; "                           \
+			 "pushl %[number]; int $0x30; addl $12, %%esp"              \
+			 : "=a"(retval)                                             \
+			 : [number] "i"(NUMBER), [arg0] "r"(ARG0), [arg1] "r"(ARG1) \
+			 : "memory");                                               \
+		retval;                                                        \
+	})
+
+/* Invokes syscall NUMBER, passing arguments ARG0, ARG1, and
+	ARG2, and returns the return value as an `int'. */
+#define syscall3(NUMBER, ARG0, ARG1, ARG2)                                             \
+	({                                                                                  \
+		int retval;                                                                      \
+		asm volatile(                                                                    \
+			 "pushl %[arg2]; pushl %[arg1]; pushl %[arg0]; "                              \
+			 "pushl %[number]; int $0x30; addl $16, %%esp"                                \
+			 : "=a"(retval)                                                               \
+			 : [number] "i"(NUMBER), [arg0] "r"(ARG0), [arg1] "r"(ARG1), [arg2] "r"(ARG2) \
+			 : "memory");                                                                 \
+		retval;                                                                          \
+	})
+
+void halt(void)
+{
+	syscall0(SYS_HALT);
+	NOT_REACHED();
+}
+
+void exit(int status)
+{
+	syscall1(SYS_EXIT, status);
+	NOT_REACHED();
+}
+
+pid_t exec(const char* 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);
+}
+
+///////////////////////////////////////////
+void sleep(int millis){
+	return syscall1(SYS_SLEEP, millis);
+}
diff --git a/lib/user/syscall.h b/lib/user/syscall.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8c2ac12e6539c81b576394d961366b9057cbcc4
--- /dev/null
+++ b/lib/user/syscall.h
@@ -0,0 +1,54 @@
+#ifndef __LIB_USER_SYSCALL_H
+#define __LIB_USER_SYSCALL_H
+
+#include <debug.h>
+#include <stdbool.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. */
+
+/* Projects 2 and later. */
+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);
+
+//////////////////////
+void sleep(int ml);
+
+/* Project 3 and optionally project 4. */
+mapid_t mmap(int fd, void* addr);
+void munmap(mapid_t);
+
+/* Project 4 only. */
+bool chdir(const char* dir);
+bool mkdir(const char* dir);
+bool readdir(int fd, char name[READDIR_MAX_LEN + 1]);
+bool isdir(int fd);
+int inumber(int fd);
+
+///////////////////////////////////////
+void sleep(int millis);
+
+#endif /* lib/user/syscall.h */
diff --git a/lib/user/user.lds b/lib/user/user.lds
new file mode 100644
index 0000000000000000000000000000000000000000..ba4f40dd90bd92f86054353b07152f46e392ed62
--- /dev/null
+++ b/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) _end_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/lib/ustar.c b/lib/ustar.c
new file mode 100644
index 0000000000000000000000000000000000000000..0c4d6bbacec59d411c712fc7327c9c130727d0a0
--- /dev/null
+++ b/lib/ustar.c
@@ -0,0 +1,214 @@
+#include <limits.h>
+#include <packed.h>
+#include <stdio.h>
+#include <string.h>
+#include <ustar.h>
+
+/* Header for ustar-format tar archive.  See the documentation of
+	the "pax" utility in [SUSv3] for the the "ustar" format
+	specification. */
+struct ustar_header {
+	char name[100];	  /* File name.  Null-terminated if room. */
+	char mode[8];		  /* Permissions as octal string. */
+	char uid[8];		  /* User ID as octal string. */
+	char gid[8];		  /* Group ID as octal string. */
+	char size[12];		  /* File size in bytes as octal string. */
+	char mtime[12];	  /* Modification time in seconds
+								  from Jan 1, 1970, as octal string. */
+	char chksum[8];	  /* Sum of octets in header as octal string. */
+	char typeflag;		  /* An enum ustar_type value. */
+	char linkname[100]; /* Name of link target.
+								  Null-terminated if room. */
+	char magic[6];		  /* "ustar\0" */
+	char version[2];	  /* "00" */
+	char uname[32];	  /* User name, always null-terminated. */
+	char gname[32];	  /* Group name, always null-terminated. */
+	char devmajor[8];	  /* Device major number as octal string. */
+	char devminor[8];	  /* Device minor number as octal string. */
+	char prefix[155];	  /* Prefix to file name.
+								  Null-terminated if room. */
+	char padding[12];	  /* Pad to 512 bytes. */
+} PACKED;
+
+/* Returns the checksum for the given ustar format HEADER. */
+static unsigned int calculate_chksum(const struct ustar_header* h)
+{
+	const uint8_t* header = (const uint8_t*) h;
+	unsigned int chksum;
+	size_t i;
+
+	chksum = 0;
+	for (i = 0; i < USTAR_HEADER_SIZE; i++) {
+		/* The ustar checksum is calculated as if the chksum field
+			were all spaces. */
+		const size_t chksum_start = offsetof(struct ustar_header, chksum);
+		const size_t chksum_end = chksum_start + sizeof h->chksum;
+		bool in_chksum_field = i >= chksum_start && i < chksum_end;
+		chksum += in_chksum_field ? ' ' : header[i];
+	}
+	return chksum;
+}
+
+/* Drop possibly dangerous prefixes from FILE_NAME and return the
+	stripped name.  An archive with file names that start with "/"
+	or "../" could cause a naive tar extractor to write to
+	arbitrary parts of the file system, not just the destination
+	directory.  We don't want to create such archives or be such a
+	naive extractor.
+
+	The return value can be a suffix of FILE_NAME or a string
+	literal. */
+static const char* strip_antisocial_prefixes(const char* file_name)
+{
+	while (*file_name == '/' || !memcmp(file_name, "./", 2)
+			 || !memcmp(file_name, "../", 3))
+		file_name = strchr(file_name, '/') + 1;
+	return *file_name == '\0' || !strcmp(file_name, "..") ? "." : file_name;
+}
+
+/* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive
+	header in ustar format for a SIZE-byte file named FILE_NAME of
+	the given TYPE.  The caller is responsible for writing the
+	header to a file or device.
+
+	If successful, returns true.  On failure (due to an
+	excessively long file name), returns false. */
+bool ustar_make_header(
+	 const char* file_name,
+	 enum ustar_type type,
+	 int size,
+	 char header[USTAR_HEADER_SIZE])
+{
+	struct ustar_header* h = (struct ustar_header*) header;
+
+	ASSERT(sizeof(struct ustar_header) == USTAR_HEADER_SIZE);
+	ASSERT(type == USTAR_REGULAR || type == USTAR_DIRECTORY);
+
+	/* Check file name. */
+	file_name = strip_antisocial_prefixes(file_name);
+	if (strlen(file_name) > 99) {
+		printf("%s: file name too long\n", file_name);
+		return false;
+	}
+
+	/* Fill in header except for final checksum. */
+	memset(h, 0, sizeof *h);
+	strlcpy(h->name, file_name, sizeof h->name);
+	snprintf(h->mode, sizeof h->mode, "%07o", type == USTAR_REGULAR ? 0644 : 0755);
+	strlcpy(h->uid, "0000000", sizeof h->uid);
+	strlcpy(h->gid, "0000000", sizeof h->gid);
+	snprintf(h->size, sizeof h->size, "%011o", size);
+	snprintf(h->mtime, sizeof h->size, "%011o", 1136102400);
+	h->typeflag = type;
+	strlcpy(h->magic, "ustar", sizeof h->magic);
+	h->version[0] = h->version[1] = '0';
+	strlcpy(h->gname, "root", sizeof h->gname);
+	strlcpy(h->uname, "root", sizeof h->uname);
+
+	/* Compute and fill in final checksum. */
+	snprintf(h->chksum, sizeof h->chksum, "%07o", calculate_chksum(h));
+
+	return true;
+}
+
+/* Parses a SIZE-byte octal field in S in the format used by
+	ustar format.  If successful, stores the field's value in
+	*VALUE and returns true; on failure, returns false.
+
+	ustar octal fields consist of a sequence of octal digits
+	terminated by a space or a null byte.  The ustar specification
+	seems ambiguous as to whether these fields must be padded on
+	the left with '0's, so we accept any field that fits in the
+	available space, regardless of whether it fills the space. */
+static bool parse_octal_field(const char* s, size_t size, unsigned long int* value)
+{
+	size_t ofs;
+
+	*value = 0;
+	for (ofs = 0; ofs < size; ofs++) {
+		char c = s[ofs];
+		if (c >= '0' && c <= '7') {
+			if (*value > ULONG_MAX / 8) {
+				/* Overflow. */
+				return false;
+			}
+			*value = c - '0' + *value * 8;
+		}
+		else if (c == ' ' || c == '\0') {
+			/* End of field, but disallow completely empty
+				fields. */
+			return ofs > 0;
+		}
+		else {
+			/* Bad character. */
+			return false;
+		}
+	}
+
+	/* Field did not end in space or null byte. */
+	return false;
+}
+
+/* Returns true if the CNT bytes starting at BLOCK are all zero,
+	false otherwise. */
+static bool is_all_zeros(const char* block, size_t cnt)
+{
+	while (cnt-- > 0)
+		if (*block++ != 0)
+			return false;
+	return true;
+}
+
+/* Parses HEADER as a ustar-format archive header for a regular
+	file or directory.  If successful, stores the archived file's
+	name in *FILE_NAME (as a pointer into HEADER or a string
+	literal), its type in *TYPE, and its size in bytes in *SIZE,
+	and returns a null pointer.  On failure, returns a
+	human-readable error message. */
+const char* ustar_parse_header(
+	 const char header[USTAR_HEADER_SIZE],
+	 const char** file_name,
+	 enum ustar_type* type,
+	 int* size)
+{
+	const struct ustar_header* h = (const struct ustar_header*) header;
+	unsigned long int chksum, size_ul;
+
+	ASSERT(sizeof(struct ustar_header) == USTAR_HEADER_SIZE);
+
+	/* Detect end of archive. */
+	if (is_all_zeros(header, USTAR_HEADER_SIZE)) {
+		*file_name = NULL;
+		*type = USTAR_EOF;
+		*size = 0;
+		return NULL;
+	}
+
+	/* Validate ustar header. */
+	if (memcmp(h->magic, "ustar", 6))
+		return "not a ustar archive";
+	else if (h->version[0] != '0' || h->version[1] != '0')
+		return "invalid ustar version";
+	else if (!parse_octal_field(h->chksum, sizeof h->chksum, &chksum))
+		return "corrupt chksum field";
+	else if (chksum != calculate_chksum(h))
+		return "checksum mismatch";
+	else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0')
+		return "file name too long";
+	else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY)
+		return "unimplemented file type";
+	if (h->typeflag == USTAR_REGULAR) {
+		if (!parse_octal_field(h->size, sizeof h->size, &size_ul))
+			return "corrupt file size field";
+		else if (size_ul > INT_MAX)
+			return "file too large";
+	}
+	else
+		size_ul = 0;
+
+	/* Success. */
+	*file_name = strip_antisocial_prefixes(h->name);
+	*type = h->typeflag;
+	*size = size_ul;
+	return NULL;
+}
diff --git a/lib/ustar.h b/lib/ustar.h
new file mode 100644
index 0000000000000000000000000000000000000000..adab04177b22a17c8e5079415d14d4a611ff446e
--- /dev/null
+++ b/lib/ustar.h
@@ -0,0 +1,30 @@
+#ifndef __LIB_USTAR_H
+#define __LIB_USTAR_H
+
+/* Support for the standard Posix "ustar" format.  See the
+	documentation of the "pax" utility in [SUSv3] for the the
+	"ustar" format specification. */
+
+#include <stdbool.h>
+
+/* Type of a file entry in an archive.
+	The values here are the bytes that appear in the file format.
+	Only types of interest to Pintos are listed here. */
+enum ustar_type {
+	USTAR_REGULAR = '0',	  /* Ordinary file. */
+	USTAR_DIRECTORY = '5', /* Directory. */
+	USTAR_EOF = -1			  /* End of archive (not an official value). */
+};
+
+/* Size of a ustar archive header, in bytes. */
+#define USTAR_HEADER_SIZE 512
+
+bool ustar_make_header(
+	 const char* file_name, enum ustar_type, int size, char header[USTAR_HEADER_SIZE]);
+const char* ustar_parse_header(
+	 const char header[USTAR_HEADER_SIZE],
+	 const char** file_name,
+	 enum ustar_type*,
+	 int* size);
+
+#endif /* lib/ustar.h */
diff --git a/standalone/gdb/.gitignore b/standalone/gdb/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..bdc150bd75c93e82d9d64a6f1c53382345d3fa31
--- /dev/null
+++ b/standalone/gdb/.gitignore
@@ -0,0 +1,3 @@
+debug1
+debug2
+debug3
diff --git a/standalone/gdb/Makefile b/standalone/gdb/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e92d37f897c9533db0a2cf76bcd508dc87b18684
--- /dev/null
+++ b/standalone/gdb/Makefile
@@ -0,0 +1,14 @@
+CFLAGS := -Wall -Wextra -std=gnu99 -pedantic -g
+TARGETS := debug1 debug2 debug3
+
+all: $(TARGETS)
+
+$(TARGETS): %: %.c memory.c
+	gcc $(CFLAGS) -o $@ $^
+
+# Disable built-in rules.
+.SUFFIXES:
+
+.PHONY: clean all
+clean:
+	rm -f $(TARGETS)
diff --git a/standalone/gdb/debug1.c b/standalone/gdb/debug1.c
new file mode 100644
index 0000000000000000000000000000000000000000..0b318a8ce1aab9d65e5a5d267928626ce98dcba6
--- /dev/null
+++ b/standalone/gdb/debug1.c
@@ -0,0 +1,49 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include "memory.h"
+
+/* Recommended compile command:
+ *
+ * make debug1
+ *
+ * Run your code:
+ *
+ * ./debug1
+ *
+ * Debug with GDB:
+ *
+ * gdb -tui ./debug1
+ */
+
+
+int main(void)
+{
+  int values = 5;
+
+  // This is a normal array of integers
+  int *contents = malloc(sizeof(int) * values);
+
+  // This is an array of pointers to integers
+  // Hint: int *data[];
+  int **data = malloc(sizeof(int *) * values);
+
+  // Fill with data:
+  for (int i = 0; i < values; i++) {
+    contents[i] = i * 5;
+    data[i] = &contents[i];
+  }
+
+  // Print data:
+  for (int i = 0; i < values; i++) {
+    int *pointer = data[i];
+    int value = *pointer;
+    printf("At %d: %d\n", i, value);
+  }
+
+  free(data);
+  free(contents);
+
+  return 0;
+}
diff --git a/standalone/gdb/debug2.c b/standalone/gdb/debug2.c
new file mode 100644
index 0000000000000000000000000000000000000000..378cb966890235a8f2acd4b61b5583ff847ad9ba
--- /dev/null
+++ b/standalone/gdb/debug2.c
@@ -0,0 +1,76 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <time.h>
+
+/* Recommended compile command:
+ *
+ * make debug2
+ *
+ * Run your code:
+ *
+ * ./debug2
+ *
+ * Debug with GDB:
+ *
+ * gdb -tui ./debug2
+ */
+
+
+// Create an array of integers.
+// Note: The randomness is not that great, but it doesn't
+// matter for this use case.
+int *create_numbers(int count)
+{
+  srand(time(NULL));
+
+  int *result = malloc(sizeof(int) * count);
+
+  for (int i = 0; i < count; i++)
+    result[i] = rand() % 512;
+
+  return result;
+}
+
+// Print out an array of integers.
+void print_numbers(int *numbers, int count)
+{
+
+  for (int i = 0; i < count; i++) {
+    int number = numbers[i];
+    printf("Number %d: %d\n", i, number);
+  }
+  
+
+}
+
+// Print out integers with a header.
+void print_with_header(const char *header, int *numbers, int count)
+{
+  printf("%s\n", header);
+  printf("------------------\n");
+
+  print_numbers(numbers, count);
+}
+
+int main(void)
+{
+  int count = 12;
+  int *numbers = create_numbers(count);
+  print_with_header("First time:", numbers, count);
+  printf("\n");
+
+
+
+
+  
+  
+  
+
+  print_with_header("Second time:", numbers, count);
+  
+  free(numbers);
+
+  return 0;
+}
diff --git a/standalone/gdb/debug3.c b/standalone/gdb/debug3.c
new file mode 100644
index 0000000000000000000000000000000000000000..757b1c10cc3b6f9dfec6ded5e96e0c822ef1048e
--- /dev/null
+++ b/standalone/gdb/debug3.c
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+
+/* Recommended compile command:
+ *
+ * make debug3
+ *
+ * Run your code:
+ *
+ * ./debug3
+ *
+ * Debug with GDB:
+ *
+ * gdb -tui ./debug3
+ */
+
+
+// Create a copy of a string. Basically, this should work just like "strdup" in
+// the standard library (see "man strdup" for details).
+char *my_strdup(const char *original)
+{
+  int length = strlen(original);
+  
+  char *copy = malloc(sizeof(char) * length);
+  for (int i = 0; i <= length; i++)
+    copy[i] = original[i];
+  return copy;
+}
+
+
+int main(void)
+{
+  const char *original = "Strings in C are fun!";
+  char *copy = my_strdup(original);
+
+  printf("Original: %s\n", original);
+  printf("Copy:     %s\n", copy);
+
+  free(copy);
+
+  return 0;
+}
diff --git a/standalone/gdb/memory.c b/standalone/gdb/memory.c
new file mode 100644
index 0000000000000000000000000000000000000000..737956c733e4524de414220e1254db55124c8dbe
--- /dev/null
+++ b/standalone/gdb/memory.c
@@ -0,0 +1,40 @@
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+struct alloc_info
+{
+  void *page_base;
+  size_t alloc_size;
+};
+
+size_t align(size_t sz, size_t alignment)
+{
+  return (sz + alignment - 1) & ~(alignment - 1);
+}
+
+void *malloc(size_t size)
+{
+  // Minimum alignment for data structures.
+  size = align(size, 32);
+
+  size_t pagesize = sysconf(_SC_PAGE_SIZE);
+  size_t round_size = align(size + sizeof(struct alloc_info), pagesize) + pagesize;
+  void *mem = mmap(NULL, round_size + pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  // Add a "guard page" that will fault.
+  mprotect((char *)mem + round_size, pagesize, PROT_NONE);
+
+  memset(mem, 0xCC, round_size);
+
+  struct alloc_info *info = (void *)((char *)mem + round_size - size - sizeof(struct alloc_info));
+  info->page_base = mem;
+  info->alloc_size = round_size;
+
+  return (char *)info + sizeof(struct alloc_info);
+}
+
+void free(void *mem)
+{
+  struct alloc_info *info = (void *)((char *)mem - sizeof(struct alloc_info));
+  munmap(info->page_base, info->alloc_size);
+}
diff --git a/standalone/slist/.gitignore b/standalone/slist/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ba2906d0666cf726c7eaadd2cd3db615dedfdf3a
--- /dev/null
+++ b/standalone/slist/.gitignore
@@ -0,0 +1 @@
+main
diff --git a/standalone/slist/Makefile b/standalone/slist/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..bd283efdae03d9c8ec3dcb9ba0a9f0c8c2c2ee77
--- /dev/null
+++ b/standalone/slist/Makefile
@@ -0,0 +1,9 @@
+CFLAGS := -Wall -Wextra -std=gnu99 -pedantic -g
+
+all: main.c list.c
+	cc $(CFLAGS) -o main main.c list.c
+
+clean:
+	rm -f main
+
+
diff --git a/standalone/slist/list.c b/standalone/slist/list.c
new file mode 100644
index 0000000000000000000000000000000000000000..f9b8a101d73943b7ff5f7dd0bf2f743cb2f42f7b
--- /dev/null
+++ b/standalone/slist/list.c
@@ -0,0 +1,90 @@
+#include "list.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Puts x at the end of the list */
+void append(struct list_item *first, int x){
+    
+    struct list_item *curr ;
+	struct list_item *new = NULL;
+	new = malloc(sizeof(struct list_item));
+	new->value = x;
+	new->next  = NULL;
+	
+	curr = first;
+  	while (curr != NULL) {
+		if(curr->next == NULL){
+			curr->next = new;
+			curr = new;
+		}
+    	curr = curr->next;}
+
+}
+
+/* Puts x at the start of the list */
+void prepend(struct list_item *first, int x){ 
+	
+  	struct list_item *new = NULL;
+	new = malloc(sizeof(struct list_item));
+
+	new->value = first->value;
+	new->next  = first->next;
+
+	first->value = x;
+	first->next = new;
+
+	}
+
+/* Find the first element in the list larger than x and
+ * put x right before that element */
+void input_sorted(struct list_item *first, int x){
+	struct list_item *curr ;
+	struct list_item *temp ;
+	struct list_item *new = NULL;
+	new = malloc(sizeof(struct list_item));
+	new->value = x;
+	new->next  = NULL;
+	curr = first;
+
+	while (curr != NULL) {
+		if(curr->value > x){
+			new->next = curr;
+			temp->next = new;
+			break;
+		}
+		temp = curr;
+    	curr = curr->next;
+	}
+}
+
+/* Prints all elements in the list */
+void print(struct list_item *first){
+printf("\n");
+
+	while (first != NULL) {
+	 	printf("%d ", first->value);
+		//printf("%d ",first->next);
+    	first = first->next;
+		}
+		printf("\n");
+}
+
+/* Free everything that is dynamically allocated */
+void clear(struct list_item *first){
+
+	struct list_item *temp ;
+	struct list_item *curr ;
+
+	curr = first->next;
+	while (curr != NULL) {
+		
+	 	temp = curr;
+    	curr = curr->next;
+		free(temp);
+		printf("Deleted! ");
+		}
+
+	
+	
+}
+
diff --git a/standalone/slist/list.h b/standalone/slist/list.h
new file mode 100644
index 0000000000000000000000000000000000000000..081df912a521d73ca0291d0dbaf03575468d0ae7
--- /dev/null
+++ b/standalone/slist/list.h
@@ -0,0 +1,26 @@
+#ifndef LIST_H
+#define LIST_H
+
+
+struct list_item {
+	int value;
+	struct list_item *next;
+};
+
+/* Puts x at the end of the list */
+void append(struct list_item *first, int x);
+
+/* Puts x at the start of the list */
+void prepend(struct list_item *first, int x);
+
+/* Find the first element in the list larger than x and
+ * put x right before that element */
+void input_sorted(struct list_item *first, int x);
+
+/* Prints all elements in the list */
+void print(struct list_item *first);
+
+/* Free everything that is dynamically allocated */
+void clear(struct list_item *first);
+
+#endif
diff --git a/standalone/slist/main.c b/standalone/slist/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..e8a188d2fc37da06ac21b7ce20f6df9949b451e3
--- /dev/null
+++ b/standalone/slist/main.c
@@ -0,0 +1,32 @@
+#include "list.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+int main()
+{
+	struct list_item root;
+	
+	root.value = -1;
+	root.next = NULL;
+	
+	append(&root , 4);
+	append(&root, 2);
+	append(&root, 3);
+
+	input_sorted(&root,1);
+	input_sorted(&root,3);
+	input_sorted(&root,2);
+
+	prepend(&root, 10);
+
+	print(&root);
+	clear(&root);
+
+   
+
+
+	
+	/* Write your test cases here */
+	return 0;
+}
+
diff --git a/tests/Algorithm/Diff.pm b/tests/Algorithm/Diff.pm
new file mode 100644
index 0000000000000000000000000000000000000000..904c530ab0997f3e776e6f83549649c6f1f596cd
--- /dev/null
+++ b/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/tests/Make.tests b/tests/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..32494ca104f3c62056e24b69bb3e845e3619a0d4
--- /dev/null
+++ b/tests/Make.tests
@@ -0,0 +1,77 @@
+# -*- makefile -*-
+
+include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS))
+
+PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS))
+TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS))
+EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES))
+
+OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES))
+ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES))
+RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES))
+
+ifdef PROGS
+include ../../Makefile.userprog
+endif
+
+TIMEOUT = 60
+
+clean::
+	rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) 
+
+grade:: results
+	$(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@
+
+check:: results
+	@cat $<
+	@COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ 	]//g;'`"; \
+	FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ 	]//g;'`"; \
+	if [ $$FAILURES = 0 ]; then					  \
+		echo "All $$COUNT tests passed.";			  \
+	else								  \
+		echo "$$FAILURES of $$COUNT tests failed.";		  \
+		exit 1;							  \
+	fi
+
+results: $(RESULTS)
+	@for d in $(TESTS) $(EXTRA_GRADES); do			\
+		if echo PASS | cmp -s $$d.result -; then	\
+			echo "pass $$d";			\
+		else						\
+			echo "FAIL $$d";			\
+		fi;						\
+	done > $@
+
+outputs:: $(OUTPUTS)
+
+$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog)))
+$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES)))
+$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test)))
+$(foreach test,$(TESTS),$(eval $(test).result: $(test).output $(test).ck))
+
+# Prevent an environment variable VERBOSE from surprising us.
+VERBOSE =
+
+TESTCMD = pintos -v -k -T $(TIMEOUT)
+TESTCMD += $(SIMULATOR)
+TESTCMD += $(PINTOSOPTS)
+ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
+TESTCMD += $(FILESYSSOURCE)
+TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file)))
+endif
+ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
+TESTCMD += --swap-size=4
+endif
+TESTCMD += -- -q
+TESTCMD += $(KERNELFLAGS)
+ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog)
+TESTCMD += -f
+endif
+TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F))
+TESTCMD += < /dev/null
+TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output
+%.output: kernel.bin loader.bin
+	$(TESTCMD)
+
+%.result: %.ck %.output
+	perl -I$(SRCDIR) $< $* $@
diff --git a/tests/arc4.c b/tests/arc4.c
new file mode 100644
index 0000000000000000000000000000000000000000..6991f439e7230fdc93090fe9a48e9ece09c277ee
--- /dev/null
+++ b/tests/arc4.c
@@ -0,0 +1,48 @@
+#include "tests/arc4.h"
+
+#include <stdint.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/tests/arc4.h b/tests/arc4.h
new file mode 100644
index 0000000000000000000000000000000000000000..2b6d0873c077eb9077c8e5f556e6dc2f132e5926
--- /dev/null
+++ b/tests/arc4.h
@@ -0,0 +1,16 @@
+#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/tests/arc4.pm b/tests/arc4.pm
new file mode 100644
index 0000000000000000000000000000000000000000..df19216cead5d9feafc9da6a10cfcb7362e1649a
--- /dev/null
+++ b/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/tests/cksum.c b/tests/cksum.c
new file mode 100644
index 0000000000000000000000000000000000000000..f028deb0192d5a7117dd21420afa138e8adcfcd2
--- /dev/null
+++ b/tests/cksum.c
@@ -0,0 +1,73 @@
+/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */
+
+#include "tests/cksum.h"
+
+#include <stdint.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/tests/cksum.h b/tests/cksum.h
new file mode 100644
index 0000000000000000000000000000000000000000..df8afe528c0460757e50d687d0f78f182ea479b4
--- /dev/null
+++ b/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/tests/cksum.pm b/tests/cksum.pm
new file mode 100644
index 0000000000000000000000000000000000000000..73be5f2e0c3723b28f1590cb662d1a1b70ed5296
--- /dev/null
+++ b/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/tests/dagjo/Make.tests b/tests/dagjo/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..5ef8c320755642f47d10ca28639359e64986581f
--- /dev/null
+++ b/tests/dagjo/Make.tests
@@ -0,0 +1,18 @@
+# -*- makefile -*-
+
+tests/%.output: FILESYSSOURCE = --filesys-size=4
+tests/%.output: PUTFILES = $(filter-out kernel.bin loader.bin, $^)
+
+tests/dagjo_TESTS = $(addprefix tests/dagjo/,recursor-p)
+
+tests/dagjo_PROGS = $(tests/dagjo_TESTS)  $(addprefix \
+tests/dagjo/,recursor-c)
+
+# recursor_ng
+tests/dagjo/recursor-p_SRC = tests/dagjo/recursor-p.c
+tests/dagjo/recursor-c_SRC = tests/dagjo/recursor-c.c 
+tests/dagjo/recursor-p_PUTFILES += tests/dagjo/recursor-c
+
+$(foreach prog,$(tests/dagjo_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/dagjo/recursor-p.output: PINTOSOPTS = --mem=128
diff --git a/tests/dagjo/recursor-c.c b/tests/dagjo/recursor-c.c
new file mode 100644
index 0000000000000000000000000000000000000000..6cf9a14e11e9b776a1cf1d2b182745cb4c4dd4e8
--- /dev/null
+++ b/tests/dagjo/recursor-c.c
@@ -0,0 +1,55 @@
+#include "../lib.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+#define MAX_SPAWN 100
+
+int main(int argc, char* argv[])
+{
+	if (argc != 4) {
+		return 1;
+	}
+
+	test_name = argv[0];
+	quiet = true;
+
+	char buffer[128];
+	pid_t pid[MAX_SPAWN];
+	int retval = 0;
+	int i;
+	int ret = 0;
+
+	/* Print args. */
+	// printf ("%s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]);
+	// printf("+");
+	/* Execute child and wait for it to finish if requested. */
+	int spawn = atoi(argv[2]);
+
+	if (spawn != 0) {
+		for (i = 0; i < spawn; i++) {
+			snprintf(
+				 buffer,
+				 sizeof buffer,
+				 "recursor-c %s %d %s",
+				 argv[1],
+				 atoi(argv[2]) - 1,
+				 argv[3]);
+			pid[i] = exec(buffer);
+		}
+		if (atoi(argv[3])) {
+			for (i = 0; i < spawn; i++) {
+				retval = wait(pid[i]);
+				if (retval < 0)
+					ret = 1;
+			}
+		}
+
+		/* Done. */
+		if (ret)
+			fail("YOU HAVE FAILED\n");
+	}
+
+	return ret;
+}
diff --git a/tests/dagjo/recursor-p.c b/tests/dagjo/recursor-p.c
new file mode 100644
index 0000000000000000000000000000000000000000..d13a1732e7ac235956f582c358f4ccdee17fedf2
--- /dev/null
+++ b/tests/dagjo/recursor-p.c
@@ -0,0 +1,32 @@
+/* dagjo@ida (convert to built-in pintos test)
+ */
+
+#include "../lib.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+const char* test_name = "recursor_ng";
+
+int main(int argc, char* argv[])
+{
+	quiet = true;
+
+	int retval = 0;
+
+	/* Print args. */
+	// printf ("%s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]);
+	// printf("+");
+	/* Execute child and wait for it to finish if requested. */
+
+	msg("begin");
+	retval = wait(exec("recursor-c pintosmaster 6 1"));
+	if (retval)
+		fail("You have failed");
+
+	msg("end");
+	printf("recursor_ng done\n");
+
+	return retval;
+}
diff --git a/tests/dagjo/recursor-p.ck b/tests/dagjo/recursor-p.ck
new file mode 100644
index 0000000000000000000000000000000000000000..788185c50244d29cb987ad2a390d46af2be3223c
--- /dev/null
+++ b/tests/dagjo/recursor-p.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+recursor_ng done
+EOF
+pass;
diff --git a/tests/filesys/Grading.no-vm b/tests/filesys/Grading.no-vm
new file mode 100644
index 0000000000000000000000000000000000000000..ee98fc1f2827b320940e27d90e399b9f98e48396
--- /dev/null
+++ b/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/tests/filesys/Grading.with-vm b/tests/filesys/Grading.with-vm
new file mode 100644
index 0000000000000000000000000000000000000000..e7c041e497c887c998d89901dd28874f03f2fc5a
--- /dev/null
+++ b/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/tests/filesys/base/Make.tests b/tests/filesys/base/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..e475222a4c9d7a1a1d5cc218e58210509a484add
--- /dev/null
+++ b/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/tests/filesys/base/Rubric b/tests/filesys/base/Rubric
new file mode 100644
index 0000000000000000000000000000000000000000..49a9d15949ee31350fec402b4ca195602d62606a
--- /dev/null
+++ b/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/tests/filesys/base/child-syn-read.c b/tests/filesys/base/child-syn-read.c
new file mode 100644
index 0000000000000000000000000000000000000000..640e6cc9531c4a601967cf5c477e0d665a62c9cc
--- /dev/null
+++ b/tests/filesys/base/child-syn-read.c
@@ -0,0 +1,41 @@
+/* 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 "tests/filesys/base/syn-read.h"
+#include "tests/lib.h"
+
+#include <random.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+static char buf[BUF_SIZE];
+
+int main(int argc, const char* argv[])
+{
+	int child_idx;
+	int fd;
+	size_t i;
+
+	test_name = "child-syn-read";
+	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/tests/filesys/base/child-syn-wrt.c b/tests/filesys/base/child-syn-wrt.c
new file mode 100644
index 0000000000000000000000000000000000000000..a7764e219393b8febe704c4b0634bbed5813d650
--- /dev/null
+++ b/tests/filesys/base/child-syn-wrt.c
@@ -0,0 +1,37 @@
+/* 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 "tests/filesys/base/syn-write.h"
+#include "tests/lib.h"
+
+#include <random.h>
+#include <stdlib.h>
+#include <syscall.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/tests/filesys/base/full.inc b/tests/filesys/base/full.inc
new file mode 100644
index 0000000000000000000000000000000000000000..38a03969045f050540c258440775211a378180c8
--- /dev/null
+++ b/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/tests/filesys/base/lg-create.c b/tests/filesys/base/lg-create.c
new file mode 100644
index 0000000000000000000000000000000000000000..551611e1427941d068f35484f4616f0a83aa4ba9
--- /dev/null
+++ b/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/tests/filesys/base/lg-create.ck b/tests/filesys/base/lg-create.ck
new file mode 100644
index 0000000000000000000000000000000000000000..86b2c513b778a0176c10eca37f44ec08112d3afd
--- /dev/null
+++ b/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/tests/filesys/base/lg-full.c b/tests/filesys/base/lg-full.c
new file mode 100644
index 0000000000000000000000000000000000000000..c6bdadef96361cc226e7fcd41d9e2daf8dda0c38
--- /dev/null
+++ b/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/tests/filesys/base/lg-full.ck b/tests/filesys/base/lg-full.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ee6c7f9e66ed5204a0937d9630d21250880abf4d
--- /dev/null
+++ b/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/tests/filesys/base/lg-random.c b/tests/filesys/base/lg-random.c
new file mode 100644
index 0000000000000000000000000000000000000000..538813da2b9dba3319d1ea2472461d290afefb6d
--- /dev/null
+++ b/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/tests/filesys/base/lg-random.ck b/tests/filesys/base/lg-random.ck
new file mode 100644
index 0000000000000000000000000000000000000000..dd9f1dd0f590ed6ca95181cb327e6f4a85333ac7
--- /dev/null
+++ b/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/tests/filesys/base/lg-seq-block.c b/tests/filesys/base/lg-seq-block.c
new file mode 100644
index 0000000000000000000000000000000000000000..7f12397178f4c98e5d870f202f8d76faed8df733
--- /dev/null
+++ b/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/tests/filesys/base/lg-seq-block.ck b/tests/filesys/base/lg-seq-block.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b78908119e73409581dd3161976a94ee50e9712f
--- /dev/null
+++ b/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/tests/filesys/base/lg-seq-random.c b/tests/filesys/base/lg-seq-random.c
new file mode 100644
index 0000000000000000000000000000000000000000..223b13d87acefe32143c89bfafeaec786e0a91fc
--- /dev/null
+++ b/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/tests/filesys/base/lg-seq-random.ck b/tests/filesys/base/lg-seq-random.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6b2dc82a199faa93df8e627fce9981ef31b5bb49
--- /dev/null
+++ b/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/tests/filesys/base/random.inc b/tests/filesys/base/random.inc
new file mode 100644
index 0000000000000000000000000000000000000000..eeeea68656408efc8e29e7bad8ba4ff013ead40d
--- /dev/null
+++ b/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/tests/filesys/base/seq-block.inc b/tests/filesys/base/seq-block.inc
new file mode 100644
index 0000000000000000000000000000000000000000..d4c1f57415bff3bb4890461fd7c59e3f0b9d0b8e
--- /dev/null
+++ b/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/tests/filesys/base/seq-random.inc b/tests/filesys/base/seq-random.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a4da4c5e36ac2f3f85c38b3497f7f2e8ee154254
--- /dev/null
+++ b/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/tests/filesys/base/sm-create.c b/tests/filesys/base/sm-create.c
new file mode 100644
index 0000000000000000000000000000000000000000..c2e08c54cbcd3e21898e5612ee0254177dad89fc
--- /dev/null
+++ b/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/tests/filesys/base/sm-create.ck b/tests/filesys/base/sm-create.ck
new file mode 100644
index 0000000000000000000000000000000000000000..8ca80dc30d5bd3cf051cfd5b794c0e5a80f3a77b
--- /dev/null
+++ b/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/tests/filesys/base/sm-full.c b/tests/filesys/base/sm-full.c
new file mode 100644
index 0000000000000000000000000000000000000000..96fe1a47406ebdeecc7657355eb49fab7381dc16
--- /dev/null
+++ b/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/tests/filesys/base/sm-full.ck b/tests/filesys/base/sm-full.ck
new file mode 100644
index 0000000000000000000000000000000000000000..2e0eb3676b3ac1961d17acabb4daa3fdc5ba8505
--- /dev/null
+++ b/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/tests/filesys/base/sm-random.c b/tests/filesys/base/sm-random.c
new file mode 100644
index 0000000000000000000000000000000000000000..6b5b04e2abc35824d37127333b91bba5eb951d30
--- /dev/null
+++ b/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/tests/filesys/base/sm-random.ck b/tests/filesys/base/sm-random.ck
new file mode 100644
index 0000000000000000000000000000000000000000..bda049de53e88a83ee7ea8a1cea2883ec1e2e57b
--- /dev/null
+++ b/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/tests/filesys/base/sm-seq-block.c b/tests/filesys/base/sm-seq-block.c
new file mode 100644
index 0000000000000000000000000000000000000000..380f9df4c981af090078107233a4074cdd7c1308
--- /dev/null
+++ b/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/tests/filesys/base/sm-seq-block.ck b/tests/filesys/base/sm-seq-block.ck
new file mode 100644
index 0000000000000000000000000000000000000000..0e2939d81d1deffb48e81603b184f5a265bd1e9d
--- /dev/null
+++ b/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/tests/filesys/base/sm-seq-random.c b/tests/filesys/base/sm-seq-random.c
new file mode 100644
index 0000000000000000000000000000000000000000..d73b4abf3a33f6c3550394e86038b9626692d993
--- /dev/null
+++ b/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/tests/filesys/base/sm-seq-random.ck b/tests/filesys/base/sm-seq-random.ck
new file mode 100644
index 0000000000000000000000000000000000000000..2fb368bcca5e8ec7392a3c7be9759758b256f171
--- /dev/null
+++ b/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/tests/filesys/base/syn-read.c b/tests/filesys/base/syn-read.c
new file mode 100644
index 0000000000000000000000000000000000000000..e4ffa62136551da2df01b6c50f8776f17f75c285
--- /dev/null
+++ b/tests/filesys/base/syn-read.c
@@ -0,0 +1,32 @@
+/* Spawns 10 child processes, all of which read from the same
+	file and make sure that the contents are what they should
+	be. */
+
+#include "tests/filesys/base/syn-read.h"
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <random.h>
+#include <stdio.h>
+#include <syscall.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/tests/filesys/base/syn-read.ck b/tests/filesys/base/syn-read.ck
new file mode 100644
index 0000000000000000000000000000000000000000..e2f68e80a2fe142fc6fbd039d9bddb7e1f890784
--- /dev/null
+++ b/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/tests/filesys/base/syn-read.h b/tests/filesys/base/syn-read.h
new file mode 100644
index 0000000000000000000000000000000000000000..bff80828324acf78df7f5f9fde0b6afcfd21731a
--- /dev/null
+++ b/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/tests/filesys/base/syn-remove.c b/tests/filesys/base/syn-remove.c
new file mode 100644
index 0000000000000000000000000000000000000000..78faca885c2db27a3e548f772980100f9429568d
--- /dev/null
+++ b/tests/filesys/base/syn-remove.c
@@ -0,0 +1,30 @@
+/* Verifies that a deleted file may still be written to and read
+	from. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <random.h>
+#include <string.h>
+#include <syscall.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/tests/filesys/base/syn-remove.ck b/tests/filesys/base/syn-remove.ck
new file mode 100644
index 0000000000000000000000000000000000000000..16ff11e550451ec5e4041d4bf82d1a5367cfcbfe
--- /dev/null
+++ b/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/tests/filesys/base/syn-write.c b/tests/filesys/base/syn-write.c
new file mode 100644
index 0000000000000000000000000000000000000000..cb011fa738daf6b63df78b91bb9ee6736bbc44f6
--- /dev/null
+++ b/tests/filesys/base/syn-write.c
@@ -0,0 +1,32 @@
+/* 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 "tests/filesys/base/syn-write.h"
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <random.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.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/tests/filesys/base/syn-write.ck b/tests/filesys/base/syn-write.ck
new file mode 100644
index 0000000000000000000000000000000000000000..629a7a2b793486577ce04e0e55733b2d967a1434
--- /dev/null
+++ b/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/tests/filesys/base/syn-write.h b/tests/filesys/base/syn-write.h
new file mode 100644
index 0000000000000000000000000000000000000000..71f133b789d1bcb1e30522eab67ac9b5f1981dbb
--- /dev/null
+++ b/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/tests/filesys/create.inc b/tests/filesys/create.inc
new file mode 100644
index 0000000000000000000000000000000000000000..4baf7718bdbd0d1a3b162b9a93ef936d0dedca5a
--- /dev/null
+++ b/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/tests/filesys/extended/Make.tests b/tests/filesys/extended/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..e03b98dc4fbfc69f1808ba2e8f13a902e75e1ea7
--- /dev/null
+++ b/tests/filesys/extended/Make.tests
@@ -0,0 +1,61 @@
+# -*- makefile -*-
+
+raw_tests = dir-empty-name dir-mk-tree dir-mkdir dir-open		\
+dir-over-file dir-rm-cwd dir-rm-parent dir-rm-root dir-rm-tree		\
+dir-rmdir dir-under-file dir-vine grow-create grow-dir-lg		\
+grow-file-size grow-root-lg grow-root-sm grow-seq-lg grow-seq-sm	\
+grow-sparse grow-tell grow-two-files syn-rw
+
+tests/filesys/extended_TESTS = $(patsubst %,tests/filesys/extended/%,$(raw_tests))
+tests/filesys/extended_EXTRA_GRADES = $(patsubst %,tests/filesys/extended/%-persistence,$(raw_tests))
+
+tests/filesys/extended_PROGS = $(tests/filesys/extended_TESTS) \
+tests/filesys/extended/child-syn-rw tests/filesys/extended/tar
+
+$(foreach prog,$(tests/filesys/extended_PROGS),			\
+	$(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c))
+$(foreach prog,$(tests/filesys/extended_TESTS),		\
+	$(eval $(prog)_SRC += tests/main.c))
+$(foreach prog,$(tests/filesys/extended_TESTS),		\
+	$(eval $(prog)_PUTFILES += tests/filesys/extended/tar))
+# The version of GNU make 3.80 on vine barfs if this is split at
+# the last comma.
+$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FILESYSSOURCE = --disk=tmp.dsk))
+
+tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c
+tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c
+
+tests/filesys/extended/syn-rw_PUTFILES += tests/filesys/extended/child-syn-rw
+
+tests/filesys/extended/dir-vine.output: TIMEOUT = 150
+
+GETTIMEOUT = 60
+
+GETCMD = pintos -v -k -T $(GETTIMEOUT)
+GETCMD += $(PINTOSOPTS)
+GETCMD += $(SIMULATOR)
+GETCMD += $(FILESYSSOURCE)
+GETCMD += -g fs.tar -a $(TEST).tar
+ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm)
+GETCMD += --swap-size=4
+endif
+GETCMD += -- -q
+GETCMD += $(KERNELFLAGS)
+GETCMD += run 'tar fs.tar /'
+GETCMD += < /dev/null
+GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output
+
+tests/filesys/extended/%.output: kernel.bin
+	rm -f tmp.dsk
+	pintos-mkdisk tmp.dsk --filesys-size=2
+	$(TESTCMD)
+	$(GETCMD)
+	rm -f tmp.dsk
+$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.output: tests/filesys/extended/$(raw_test).output))
+$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.result: tests/filesys/extended/$(raw_test).result))
+
+TARS = $(addsuffix .tar,$(tests/filesys/extended_TESTS))
+
+clean::
+	rm -f $(TARS)
+	rm -f tests/filesys/extended/can-rmdir-cwd
diff --git a/tests/filesys/extended/Rubric.functionality b/tests/filesys/extended/Rubric.functionality
new file mode 100644
index 0000000000000000000000000000000000000000..91ed6f08cb658bb9b1444ad4ca83c21f71da9f71
--- /dev/null
+++ b/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/tests/filesys/extended/Rubric.persistence b/tests/filesys/extended/Rubric.persistence
new file mode 100644
index 0000000000000000000000000000000000000000..405620a073f037e58b16b8fc12deae28df18e9cf
--- /dev/null
+++ b/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/tests/filesys/extended/Rubric.robustness b/tests/filesys/extended/Rubric.robustness
new file mode 100644
index 0000000000000000000000000000000000000000..fb9f32fe2cd7880b0a8304f0e6fe0e9a19f5c2db
--- /dev/null
+++ b/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/tests/filesys/extended/child-syn-rw.c b/tests/filesys/extended/child-syn-rw.c
new file mode 100644
index 0000000000000000000000000000000000000000..e2616798846141367142817bc2bfbe45856f004c
--- /dev/null
+++ b/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 "tests/filesys/extended/syn-rw.h"
+#include "tests/lib.h"
+
+#include <random.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+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;
+
+	test_name = "child-syn-rw";
+	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/tests/filesys/extended/dir-empty-name-persistence.ck b/tests/filesys/extended/dir-empty-name-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..562c45102a6fbc2fea2733100ddeba700eaa06d9
--- /dev/null
+++ b/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/tests/filesys/extended/dir-empty-name.c b/tests/filesys/extended/dir-empty-name.c
new file mode 100644
index 0000000000000000000000000000000000000000..0b5bf14769bdbe9a531152ac25e47dc2ff90fb08
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	CHECK(!mkdir(""), "mkdir \"\" (must return false)");
+}
diff --git a/tests/filesys/extended/dir-empty-name.ck b/tests/filesys/extended/dir-empty-name.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d6c5621eb5ff0147606384a3a08d1054a85d56a0
--- /dev/null
+++ b/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/tests/filesys/extended/dir-mk-tree-persistence.ck b/tests/filesys/extended/dir-mk-tree-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..fb16afd2cefd945fdf515fcefef35f1efcb992b7
--- /dev/null
+++ b/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/tests/filesys/extended/dir-mk-tree.c b/tests/filesys/extended/dir-mk-tree.c
new file mode 100644
index 0000000000000000000000000000000000000000..1d6c10ae3ecd2c5c088d4e7b92614820b0216510
--- /dev/null
+++ b/tests/filesys/extended/dir-mk-tree.c
@@ -0,0 +1,10 @@
+/* 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/tests/filesys/extended/dir-mk-tree.ck b/tests/filesys/extended/dir-mk-tree.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a8507e23fc68d2482e17b478a26bf59e7357f59b
--- /dev/null
+++ b/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/tests/filesys/extended/dir-mkdir-persistence.ck b/tests/filesys/extended/dir-mkdir-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..768290032c395bce235e73fb7bf663412c944445
--- /dev/null
+++ b/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/tests/filesys/extended/dir-mkdir.c b/tests/filesys/extended/dir-mkdir.c
new file mode 100644
index 0000000000000000000000000000000000000000..c9fbe922968162c5610f178815147cedaab2fc94
--- /dev/null
+++ b/tests/filesys/extended/dir-mkdir.c
@@ -0,0 +1,14 @@
+/* Tests mkdir(). */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/dir-mkdir.ck b/tests/filesys/extended/dir-mkdir.ck
new file mode 100644
index 0000000000000000000000000000000000000000..4644f802387c21cae6a486294cd765aeab22911d
--- /dev/null
+++ b/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/tests/filesys/extended/dir-open-persistence.ck b/tests/filesys/extended/dir-open-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..26ff2f16743f55f7cf826bf791d3e3c6164157c6
--- /dev/null
+++ b/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/tests/filesys/extended/dir-open.c b/tests/filesys/extended/dir-open.c
new file mode 100644
index 0000000000000000000000000000000000000000..3406d429377672f6e6878db18f9bf7eea1e765a7
--- /dev/null
+++ b/tests/filesys/extended/dir-open.c
@@ -0,0 +1,20 @@
+/* Opens a directory, then tries to write to it, which must
+	fail. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/dir-open.ck b/tests/filesys/extended/dir-open.ck
new file mode 100644
index 0000000000000000000000000000000000000000..fccc56352fc7b6bc9f299b66e818c6c168a7f613
--- /dev/null
+++ b/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/tests/filesys/extended/dir-over-file-persistence.ck b/tests/filesys/extended/dir-over-file-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..56b4ed29683f07f7798055b3a848846bcc9d94d6
--- /dev/null
+++ b/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/tests/filesys/extended/dir-over-file.c b/tests/filesys/extended/dir-over-file.c
new file mode 100644
index 0000000000000000000000000000000000000000..c976bcb363259e6b2c7d3690233bcb6218c629f2
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	CHECK(mkdir("abc"), "mkdir \"abc\"");
+	CHECK(!create("abc", 0), "create \"abc\" (must return false)");
+}
diff --git a/tests/filesys/extended/dir-over-file.ck b/tests/filesys/extended/dir-over-file.ck
new file mode 100644
index 0000000000000000000000000000000000000000..aae1c1e8589d8c04ec4186517752feac5d7ede2c
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-cwd-persistence.ck b/tests/filesys/extended/dir-rm-cwd-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..75335702802317d5ef47d56f22162b09fa9e5a5f
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-cwd.c b/tests/filesys/extended/dir-rm-cwd.c
new file mode 100644
index 0000000000000000000000000000000000000000..2818d7cea7ac225ea9a2b14a0359cd9f43f3c41d
--- /dev/null
+++ b/tests/filesys/extended/dir-rm-cwd.c
@@ -0,0 +1,73 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/dir-rm-cwd.ck b/tests/filesys/extended/dir-rm-cwd.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6fa47395f62338c98878d178b93ffae0351b8dda
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-parent-persistence.ck b/tests/filesys/extended/dir-rm-parent-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..f30b04aac291dea3e6f58b0869c63a0c32f7c4fe
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-parent.c b/tests/filesys/extended/dir-rm-parent.c
new file mode 100644
index 0000000000000000000000000000000000000000..1692227403c4ebc01b446587861f9abe9100b16d
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/dir-rm-parent.ck b/tests/filesys/extended/dir-rm-parent.ck
new file mode 100644
index 0000000000000000000000000000000000000000..9fea8f23c79348a51c8804b2d582a79619f56257
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-root-persistence.ck b/tests/filesys/extended/dir-rm-root-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..631510736f95d7aa72361bc80f23f285523dcabd
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-root.c b/tests/filesys/extended/dir-rm-root.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e110fdb86270223db8aef9c895f7900c3a05d5c
--- /dev/null
+++ b/tests/filesys/extended/dir-rm-root.c
@@ -0,0 +1,13 @@
+/* Try to remove the root directory.
+	This must fail. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	CHECK(!remove("/"), "remove \"/\" (must fail)");
+	CHECK(create("/a", 243), "create \"/a\"");
+}
diff --git a/tests/filesys/extended/dir-rm-root.ck b/tests/filesys/extended/dir-rm-root.ck
new file mode 100644
index 0000000000000000000000000000000000000000..8a69ff3c1f943b167bb18480ddcf390bd69007c5
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-tree-persistence.ck b/tests/filesys/extended/dir-rm-tree-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..562c45102a6fbc2fea2733100ddeba700eaa06d9
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rm-tree.c b/tests/filesys/extended/dir-rm-tree.c
new file mode 100644
index 0000000000000000000000000000000000000000..af060ac14f2ccc2f0b619d07332a976a5a578422
--- /dev/null
+++ b/tests/filesys/extended/dir-rm-tree.c
@@ -0,0 +1,55 @@
+/* Creates directories /0/0/0 through /3/2/2 and files in the
+	leaf directories, then removes them. */
+
+#include "tests/filesys/extended/mk-tree.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <syscall.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/tests/filesys/extended/dir-rm-tree.ck b/tests/filesys/extended/dir-rm-tree.ck
new file mode 100644
index 0000000000000000000000000000000000000000..587b4935a52accb5fd2683c2f5dd7e054c12b4fa
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rmdir-persistence.ck b/tests/filesys/extended/dir-rmdir-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..562c45102a6fbc2fea2733100ddeba700eaa06d9
--- /dev/null
+++ b/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/tests/filesys/extended/dir-rmdir.c b/tests/filesys/extended/dir-rmdir.c
new file mode 100644
index 0000000000000000000000000000000000000000..8adaa635bfc0bc68a5ada9b70dbedc1fb7d3dd71
--- /dev/null
+++ b/tests/filesys/extended/dir-rmdir.c
@@ -0,0 +1,14 @@
+/* Creates and removes a directory, then makes sure that it's
+	really gone. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/dir-rmdir.ck b/tests/filesys/extended/dir-rmdir.ck
new file mode 100644
index 0000000000000000000000000000000000000000..e0d892251e02c4573000e56f11e56ac11b422eb0
--- /dev/null
+++ b/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/tests/filesys/extended/dir-under-file-persistence.ck b/tests/filesys/extended/dir-under-file-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..67ca528ac06a890e8428e52961fc78d7b052a84f
--- /dev/null
+++ b/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/tests/filesys/extended/dir-under-file.c b/tests/filesys/extended/dir-under-file.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a2f9ae4ccfaed1f2ab5b61d79f7c8675758a6d1
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	CHECK(create("abc", 0), "create \"abc\"");
+	CHECK(!mkdir("abc"), "mkdir \"abc\" (must return false)");
+}
diff --git a/tests/filesys/extended/dir-under-file.ck b/tests/filesys/extended/dir-under-file.ck
new file mode 100644
index 0000000000000000000000000000000000000000..cce23b42e2a052dd502e9e0dc264d269b41568bc
--- /dev/null
+++ b/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/tests/filesys/extended/dir-vine-persistence.ck b/tests/filesys/extended/dir-vine-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..698ef01e0925152bd35525785308c3e640bb3086
--- /dev/null
+++ b/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/tests/filesys/extended/dir-vine.c b/tests/filesys/extended/dir-vine.c
new file mode 100644
index 0000000000000000000000000000000000000000..6cb49ffd532b423fe042853f9750d421305b0cfc
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.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/tests/filesys/extended/dir-vine.ck b/tests/filesys/extended/dir-vine.ck
new file mode 100644
index 0000000000000000000000000000000000000000..db452b0917621798578d14c3f2115943969c37eb
--- /dev/null
+++ b/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/tests/filesys/extended/grow-create-persistence.ck b/tests/filesys/extended/grow-create-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..bbcb24f1d2e2e0f9c015ee7e6f7a8ba90376e160
--- /dev/null
+++ b/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/tests/filesys/extended/grow-create.c b/tests/filesys/extended/grow-create.c
new file mode 100644
index 0000000000000000000000000000000000000000..9ccc4ea31b6a775634b1ca52d818602dbe7d39bd
--- /dev/null
+++ b/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/tests/filesys/extended/grow-create.ck b/tests/filesys/extended/grow-create.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b2e69d1cf35e0a91d8ec0d0dad1a1b12167dc86f
--- /dev/null
+++ b/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/tests/filesys/extended/grow-dir-lg-persistence.ck b/tests/filesys/extended/grow-dir-lg-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..989a322445837aa4f3d1f9d2fe5bdbb171980b79
--- /dev/null
+++ b/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/tests/filesys/extended/grow-dir-lg.c b/tests/filesys/extended/grow-dir-lg.c
new file mode 100644
index 0000000000000000000000000000000000000000..b87928fd0f3f97d73f31e56e892a41dc91d38a36
--- /dev/null
+++ b/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/tests/filesys/extended/grow-dir-lg.ck b/tests/filesys/extended/grow-dir-lg.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ec58bd3fd2187140641e67d2a1aa9fa984c14fc7
--- /dev/null
+++ b/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/tests/filesys/extended/grow-dir.inc b/tests/filesys/extended/grow-dir.inc
new file mode 100644
index 0000000000000000000000000000000000000000..bee0ba0dd45b1472d1c0ccabcf8129f374ceb606
--- /dev/null
+++ b/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/tests/filesys/extended/grow-file-size-persistence.ck b/tests/filesys/extended/grow-file-size-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..150f383d0d4df217d5cc30fc91c3b77a3e134895
--- /dev/null
+++ b/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/tests/filesys/extended/grow-file-size.c b/tests/filesys/extended/grow-file-size.c
new file mode 100644
index 0000000000000000000000000000000000000000..db5b456055b512b2f9b3cacd9c54ef618c8df662
--- /dev/null
+++ b/tests/filesys/extended/grow-file-size.c
@@ -0,0 +1,28 @@
+/* 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 "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/grow-file-size.ck b/tests/filesys/extended/grow-file-size.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d81feff594fa9d5dcfc7fba54c53d0a3ec465eef
--- /dev/null
+++ b/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/tests/filesys/extended/grow-root-lg-persistence.ck b/tests/filesys/extended/grow-root-lg-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1692f46c1a6e7cd35d79910deeb56a9e1bff8d93
--- /dev/null
+++ b/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/tests/filesys/extended/grow-root-lg.c b/tests/filesys/extended/grow-root-lg.c
new file mode 100644
index 0000000000000000000000000000000000000000..d8d6c0900e18476d22ce8c1c2153256de79ca12d
--- /dev/null
+++ b/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/tests/filesys/extended/grow-root-lg.ck b/tests/filesys/extended/grow-root-lg.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b174bc9c0332ef514561b5d94b0f9a072e4bbfa3
--- /dev/null
+++ b/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/tests/filesys/extended/grow-root-sm-persistence.ck b/tests/filesys/extended/grow-root-sm-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..2b0b8abe157101b5be555b13374b68645d0c0a8b
--- /dev/null
+++ b/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/tests/filesys/extended/grow-root-sm.c b/tests/filesys/extended/grow-root-sm.c
new file mode 100644
index 0000000000000000000000000000000000000000..ee375d50996ef3eba6bf021b175ae6cb17344341
--- /dev/null
+++ b/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/tests/filesys/extended/grow-root-sm.ck b/tests/filesys/extended/grow-root-sm.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1aac7e9cac77c8f7d6a6c279c8631fe59c3641a7
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq-lg-persistence.ck b/tests/filesys/extended/grow-seq-lg-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..41aaae0a8256df4694d8cf011ea603a9677a25fa
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq-lg.c b/tests/filesys/extended/grow-seq-lg.c
new file mode 100644
index 0000000000000000000000000000000000000000..04e014cbdaf413e75dd91dbf70bc929a0aa14efb
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq-lg.ck b/tests/filesys/extended/grow-seq-lg.ck
new file mode 100644
index 0000000000000000000000000000000000000000..90fcd8cceadf478b1272cf6ece651747497e8bb7
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq-sm-persistence.ck b/tests/filesys/extended/grow-seq-sm-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6cb0bd8656d5861ee0593f441c17fc6b2dab339d
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq-sm.c b/tests/filesys/extended/grow-seq-sm.c
new file mode 100644
index 0000000000000000000000000000000000000000..b372699db88b88896e801087e0cd6753ec8c5770
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq-sm.ck b/tests/filesys/extended/grow-seq-sm.ck
new file mode 100644
index 0000000000000000000000000000000000000000..5cf451850addaf0edb83e2b422beded39687a5cf
--- /dev/null
+++ b/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/tests/filesys/extended/grow-seq.inc b/tests/filesys/extended/grow-seq.inc
new file mode 100644
index 0000000000000000000000000000000000000000..1b7710c9c1d4d8a91867b17e8e5f4365fc4cd0d3
--- /dev/null
+++ b/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/tests/filesys/extended/grow-sparse-persistence.ck b/tests/filesys/extended/grow-sparse-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..3f06a5b81e98113fdd11e13b85fc24a0440f21bb
--- /dev/null
+++ b/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/tests/filesys/extended/grow-sparse.c b/tests/filesys/extended/grow-sparse.c
new file mode 100644
index 0000000000000000000000000000000000000000..7b10f79ef9afe99042bf026c60c72d14b4815007
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/grow-sparse.ck b/tests/filesys/extended/grow-sparse.ck
new file mode 100644
index 0000000000000000000000000000000000000000..379ba2c7e2552b594f3eb2cffb8dbb381d2e056e
--- /dev/null
+++ b/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/tests/filesys/extended/grow-tell-persistence.ck b/tests/filesys/extended/grow-tell-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d93a422709c68bbb4582c6de876d336d5162cac2
--- /dev/null
+++ b/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/tests/filesys/extended/grow-tell.c b/tests/filesys/extended/grow-tell.c
new file mode 100644
index 0000000000000000000000000000000000000000..48c400ab647f9a0608efbf8fe3ae252e2b2954be
--- /dev/null
+++ b/tests/filesys/extended/grow-tell.c
@@ -0,0 +1,27 @@
+/* Checks that growing a file updates the file position
+	correctly. */
+
+#include "tests/filesys/seq-test.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/filesys/extended/grow-tell.ck b/tests/filesys/extended/grow-tell.ck
new file mode 100644
index 0000000000000000000000000000000000000000..fe94707cbbbbbf7c69987cb3a4b4163d76a9fb08
--- /dev/null
+++ b/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/tests/filesys/extended/grow-two-files-persistence.ck b/tests/filesys/extended/grow-two-files-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1c4ced18ee018afc22716cea279fa8c5aec375f2
--- /dev/null
+++ b/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/tests/filesys/extended/grow-two-files.c b/tests/filesys/extended/grow-two-files.c
new file mode 100644
index 0000000000000000000000000000000000000000..d06cba8a8700435d328690e2e4aba37f24eb081a
--- /dev/null
+++ b/tests/filesys/extended/grow-two-files.c
@@ -0,0 +1,64 @@
+/* Grows two files in parallel and checks that their contents are
+	correct. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <random.h>
+#include <syscall.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/tests/filesys/extended/grow-two-files.ck b/tests/filesys/extended/grow-two-files.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b5e754ab42be6a36820e76f889f34373a29fee61
--- /dev/null
+++ b/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/tests/filesys/extended/mk-tree.c b/tests/filesys/extended/mk-tree.c
new file mode 100644
index 0000000000000000000000000000000000000000..c22096c0474285e7515e08b9f920281caf882a91
--- /dev/null
+++ b/tests/filesys/extended/mk-tree.c
@@ -0,0 +1,61 @@
+/* Library function for creating a tree of directories. */
+
+#include "tests/filesys/extended/mk-tree.h"
+
+#include "tests/lib.h"
+
+#include <stdio.h>
+#include <syscall.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/tests/filesys/extended/mk-tree.h b/tests/filesys/extended/mk-tree.h
new file mode 100644
index 0000000000000000000000000000000000000000..55599d6a9a090f65a3f1309476b8dde796e69add
--- /dev/null
+++ b/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/tests/filesys/extended/syn-rw-persistence.ck b/tests/filesys/extended/syn-rw-persistence.ck
new file mode 100644
index 0000000000000000000000000000000000000000..62d57ee995ffee2e10a081e0100bb2e404b54522
--- /dev/null
+++ b/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/tests/filesys/extended/syn-rw.c b/tests/filesys/extended/syn-rw.c
new file mode 100644
index 0000000000000000000000000000000000000000..1b8e39d8ee235e8030685687845c8ecd11ba69a8
--- /dev/null
+++ b/tests/filesys/extended/syn-rw.c
@@ -0,0 +1,39 @@
+/* Grows a file in chunks while subprocesses read the growing
+	file. */
+
+#include "tests/filesys/extended/syn-rw.h"
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <random.h>
+#include <syscall.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/tests/filesys/extended/syn-rw.ck b/tests/filesys/extended/syn-rw.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ac82aa8c77915d9b21499cac7287100b1f0338cd
--- /dev/null
+++ b/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/tests/filesys/extended/syn-rw.h b/tests/filesys/extended/syn-rw.h
new file mode 100644
index 0000000000000000000000000000000000000000..7bed26ac597c29d8e55a9517869e5e1849b98cbc
--- /dev/null
+++ b/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/tests/filesys/extended/tar.c b/tests/filesys/extended/tar.c
new file mode 100644
index 0000000000000000000000000000000000000000..e6d68da549fc73511ba7414b26fd54c0b0b55442
--- /dev/null
+++ b/tests/filesys/extended/tar.c
@@ -0,0 +1,202 @@
+/* tar.c
+
+	Creates a tar archive. */
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include <ustar.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,
+	 enum ustar_type,
+	 int size,
+	 int archive_fd,
+	 bool* write_error);
+
+static bool do_write(int fd, const char* buffer, int size, bool* write_error);
+
+static bool make_tar_archive(const char* archive_name, char* files[], size_t file_cnt)
+{
+	static const char zeros[512];
+	int archive_fd;
+	bool success = true;
+	bool write_error = false;
+	size_t i;
+
+	if (!create(archive_name, 0)) {
+		printf("%s: create failed\n", archive_name);
+		return false;
+	}
+	archive_fd = open(archive_name);
+	if (archive_fd < 0) {
+		printf("%s: open failed\n", archive_name);
+		return false;
+	}
+
+	for (i = 0; i < file_cnt; i++) {
+		char file_name[128];
+
+		strlcpy(file_name, files[i], sizeof file_name);
+		if (!archive_file(file_name, sizeof file_name, archive_fd, &write_error))
+			success = false;
+	}
+
+	if (!do_write(archive_fd, zeros, 512, &write_error)
+		 || !do_write(archive_fd, zeros, 512, &write_error))
+		success = false;
+
+	close(archive_fd);
+
+	return success;
+}
+
+static bool archive_file(
+	 char file_name[], size_t file_name_size, int archive_fd, bool* write_error)
+{
+	int file_fd = open(file_name);
+	if (file_fd >= 0) {
+		bool success;
+
+		if (inumber(file_fd) != inumber(archive_fd)) {
+			if (!isdir(file_fd))
+				success
+					 = archive_ordinary_file(file_name, file_fd, archive_fd, write_error);
+			else
+				success = archive_directory(
+					 file_name, file_name_size, file_fd, archive_fd, write_error);
+		}
+		else {
+			/* Nothing to do: don't try to archive the archive file. */
+			success = true;
+		}
+
+		close(file_fd);
+
+		return success;
+	}
+	else {
+		printf("%s: open failed\n", file_name);
+		return false;
+	}
+}
+
+static bool archive_ordinary_file(
+	 const char* file_name, int file_fd, int archive_fd, bool* write_error)
+{
+	bool read_error = false;
+	bool success = true;
+	int file_size = filesize(file_fd);
+
+	if (!write_header(file_name, USTAR_REGULAR, file_size, archive_fd, write_error))
+		return false;
+
+	while (file_size > 0) {
+		static char buf[512];
+		int chunk_size = file_size > 512 ? 512 : file_size;
+		int read_retval = read(file_fd, buf, chunk_size);
+		int bytes_read = read_retval > 0 ? read_retval : 0;
+
+		if (bytes_read != chunk_size && !read_error) {
+			printf("%s: read error\n", file_name);
+			read_error = true;
+			success = false;
+		}
+
+		memset(buf + bytes_read, 0, 512 - bytes_read);
+		if (!do_write(archive_fd, buf, 512, write_error))
+			success = false;
+
+		file_size -= chunk_size;
+	}
+
+	return success;
+}
+
+static bool archive_directory(
+	 char file_name[],
+	 size_t file_name_size,
+	 int file_fd,
+	 int archive_fd,
+	 bool* write_error)
+{
+	size_t dir_len;
+	bool success = true;
+
+	dir_len = strlen(file_name);
+	if (dir_len + 1 + READDIR_MAX_LEN + 1 > file_name_size) {
+		printf("%s: file name too long\n", file_name);
+		return false;
+	}
+
+	if (!write_header(file_name, USTAR_DIRECTORY, 0, archive_fd, write_error))
+		return false;
+
+	file_name[dir_len] = '/';
+	while (readdir(file_fd, &file_name[dir_len + 1]))
+		if (!archive_file(file_name, file_name_size, archive_fd, write_error))
+			success = false;
+	file_name[dir_len] = '\0';
+
+	return success;
+}
+
+static bool write_header(
+	 const char* file_name,
+	 enum ustar_type type,
+	 int size,
+	 int archive_fd,
+	 bool* write_error)
+{
+	static char header[512];
+	return (
+		 ustar_make_header(file_name, type, size, header)
+		 && do_write(archive_fd, header, 512, write_error));
+}
+
+static bool do_write(int fd, const char* buffer, int size, bool* write_error)
+{
+	if (write(fd, buffer, size) == size)
+		return true;
+	else {
+		if (!*write_error) {
+			printf("error writing archive\n");
+			*write_error = true;
+		}
+		return false;
+	}
+}
diff --git a/tests/filesys/seq-test.c b/tests/filesys/seq-test.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d291c5da2ca16230c449c7c702ffec21a47abb5
--- /dev/null
+++ b/tests/filesys/seq-test.c
@@ -0,0 +1,44 @@
+#include "tests/filesys/seq-test.h"
+
+#include "tests/lib.h"
+
+#include <random.h>
+#include <syscall.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/tests/filesys/seq-test.h b/tests/filesys/seq-test.h
new file mode 100644
index 0000000000000000000000000000000000000000..559af9e8761917744c3b177e8b24428ac099dd60
--- /dev/null
+++ b/tests/filesys/seq-test.h
@@ -0,0 +1,14 @@
+#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/tests/filst/Make.tests b/tests/filst/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..643e81dbcf9379edf639604f4818125de672168d
--- /dev/null
+++ b/tests/filst/Make.tests
@@ -0,0 +1,14 @@
+# -*- makefile -*-
+
+tests/%.output: FSDISK = 2
+tests/%.output: PUTFILES = $(filter-out os.dsk, $^)
+
+tests/filst_TESTS = $(addprefix tests/filst/,sc-bad-write sc-bad-close sc-bad-nr-1 sc-bad-nr-2 sc-bad-nr-3 sc-bad-align-1 sc-bad-align-2 sc-bad-exit sc-write-buf)
+
+tests/filst_PROGS = $(tests/filst_TESTS)
+
+# Semi-automatic magic.
+$(foreach prog,$(tests/filst_PROGS),$(eval $(prog)_SRC += $(prog).c))
+$(foreach prog,$(tests/filst_PROGS),$(eval $(prog)_SRC += tests/main.c))
+$(foreach prog,$(tests/filst_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
diff --git a/tests/filst/sc-bad-align-1.c b/tests/filst/sc-bad-align-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..113c150d8120fd355d0e79c4f6a23710a74b546d
--- /dev/null
+++ b/tests/filst/sc-bad-align-1.c
@@ -0,0 +1,47 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdio.h>
+#include <syscall-nr.h>
+
+/**
+ * From threads/vaddr.h:
+ */
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+#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). */
+
+static inline void* pg_round_up(const void* va)
+{
+	return (void*) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/**
+ * External symbol which address is the first address after all data in the BSS segment.
+ */
+extern int _end_bss;
+
+void test_main(void)
+{
+	// Get the address of the first unmapped page in the process.
+	unsigned page = (unsigned) pg_round_up(&_end_bss);
+
+	// Reserve space for a part of the syscall number (1 out of 4 bytes).
+	unsigned base = page - 1;
+
+	// Call a syscall.
+	asm volatile(
+		 "movl %%esp, %%edi;"	// Save the stack pointer in case we survive.
+		 "movl %0, %%esp;"		// Set stack pointer.
+		 "movb $0, (%%esp);"		// Set the first byte of the syscall number to zero.
+		 "int $0x30;"				// Trigger syscall.
+		 "movl %%edi, %%esp;"	// Restore the old stack if we survivied.
+		 :
+		 : "r"(base)
+		 : "%eax", "%edi");
+
+	fail("should have died.");
+}
diff --git a/tests/filst/sc-bad-align-1.ck b/tests/filst/sc-bad-align-1.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c280b8cb0ac46284c42ce5f44b9a8b9a4f9a434c
--- /dev/null
+++ b/tests/filst/sc-bad-align-1.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-align-1) begin
+sc-bad-align-1: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-align-2.c b/tests/filst/sc-bad-align-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..a32c0eeaee7df8263bccdc0ab8e379fd28a38b54
--- /dev/null
+++ b/tests/filst/sc-bad-align-2.c
@@ -0,0 +1,53 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdio.h>
+#include <syscall-nr.h>
+
+/**
+ * Thanks to Herman Appelgren for suggesting this test case.
+ */
+
+/**
+ * From threads/vaddr.h:
+ */
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+#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). */
+
+static inline void* pg_round_up(const void* va)
+{
+	return (void*) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/**
+ * External symbol which address is the first address after all data in the BSS segment.
+ */
+extern int _end_bss;
+
+void test_main(void)
+{
+	// Get the address of the first unmapped page in the process.
+	unsigned page = (unsigned) pg_round_up(&_end_bss);
+
+	// Reserve space for the syscall number and two out of four bytes for the syscall
+	// argument.
+	unsigned base = page - 4 - 2;
+
+	// Call a syscall.
+	asm volatile(
+		 "movl %%esp, %%edi;"			// Save the stack pointer in case we survive.
+		 "movl %0, %%esp;"				// Set stack pointer.
+		 "movl %[number], (%%esp);"	// Set syscall number.
+		 "movw $0, 4(%%esp);"			// Set the first byte of the syscall number to zero.
+		 "int $0x30;"						// Trigger syscall.
+		 "movl %%edi, %%esp;"			// Restore the old stack if we survivied.
+		 :
+		 : "r"(base), [number] "i"(SYS_EXIT)
+		 : "%eax", "%edi");
+
+	fail("should have died.");
+}
diff --git a/tests/filst/sc-bad-align-2.ck b/tests/filst/sc-bad-align-2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ed18c558d378f84bebd2524cc4fa8ca4bba913ee
--- /dev/null
+++ b/tests/filst/sc-bad-align-2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-align-2) begin
+sc-bad-align-2: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-close.c b/tests/filst/sc-bad-close.c
new file mode 100644
index 0000000000000000000000000000000000000000..688655733e048292e353f9094f32f42e1a7c5f7f
--- /dev/null
+++ b/tests/filst/sc-bad-close.c
@@ -0,0 +1,68 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <syscall-nr.h>
+
+/**
+ * From threads/vaddr.h:
+ */
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+#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). */
+
+static inline void* pg_round_up(const void* va)
+{
+	return (void*) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/**
+ * External symbol which address is the first address after all data in the BSS segment.
+ */
+extern int _end_bss;
+
+void test_main(void)
+{
+	// Get the addres of the first unmapped page in the system.
+	unsigned page = (unsigned) pg_round_up(&_end_bss);
+
+	// Reserve space for 2 parameters.
+	unsigned base = page - sizeof(int) * 2;
+
+	// Call close() with space for 2 parameters (syscall number + parameter, should be
+	// fine).
+	asm volatile(
+		 "movl %%esp, %%edi;"
+		 "movl %0, %%esp;"		// Set stack pointer to right below page boundary.
+		 "movl %1, (%%esp);"		// Try to call SYS_CLOSE
+		 "movl $8, 4(%%esp);"	// Close fileno #8
+		 "int $0x30;"
+		 "movl %%edi, %%esp;"	// Restore esp.
+		 :
+		 : "r"(base), "i"(SYS_CLOSE)
+		 : "%eax", "%edi");
+
+	write(STDOUT_FILENO, "OK\n", 3);
+
+	// Reserve space for only syscall number (close requires an additional parameter).
+	base = page - sizeof(int) * 1;
+
+	// Call close() with space for 1 parameter (the kernel should kill us for doing
+	// this).
+	asm volatile(
+		 "movl %%esp, %%edi;"
+		 "movl %0, %%esp;"	  // Set stack pointer to right below page boundary.
+		 "movl %1, (%%esp);"	  // Try to call SYS_CLOSE
+		 // "movl $8, 4(%%esp);"    // Close fileno #8
+		 "int $0x30;"
+		 "movl %%edi, %%esp;"	// Restore esp in case we do not crash (as we should).
+		 :
+		 : "r"(base), "i"(SYS_CLOSE)
+		 : "%eax", "%edi");
+
+	fail("should have died.");
+}
diff --git a/tests/filst/sc-bad-close.ck b/tests/filst/sc-bad-close.ck
new file mode 100644
index 0000000000000000000000000000000000000000..013e503fcb791888b3b1bbc6bca9ff309b7980bf
--- /dev/null
+++ b/tests/filst/sc-bad-close.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-close) begin
+OK
+sc-bad-close: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-exit.c b/tests/filst/sc-bad-exit.c
new file mode 100644
index 0000000000000000000000000000000000000000..a36cd2d6649192ffeab4c4424e3e0cdf5fff2b34
--- /dev/null
+++ b/tests/filst/sc-bad-exit.c
@@ -0,0 +1,49 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdio.h>
+#include <syscall-nr.h>
+
+/**
+ * From threads/vaddr.h:
+ */
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+#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). */
+
+static inline void* pg_round_up(const void* va)
+{
+	return (void*) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/**
+ * External symbol which address is the first address after all data in the BSS segment.
+ */
+extern int _end_bss;
+
+void test_main(void)
+{
+	// Get the address of the first unmapped page in the process.
+	unsigned page = (unsigned) pg_round_up(&_end_bss);
+
+	// Reserve space for the syscall number and a part of the exit code (1 out of 4
+	// bytes).
+	unsigned base = page - sizeof(int) - 1;
+
+	// Call a syscall.
+	asm volatile(
+		 "movl %%esp, %%edi;"	// Save the stack pointer in case we survive.
+		 "movl %0, %%esp;"		// Set stack pointer.
+		 "movl %1, (%%esp);"		// Try to call SYS_EXIT
+		 "movb $0, 4(%%esp);"	// Set the parameter to zero.
+		 "int $0x30;"				// Trigger syscall.
+		 "movl %%edi, %%esp;"	// Restore the old stack if we survivied.
+		 :
+		 : "r"(base), "i"(SYS_EXIT)
+		 : "%eax", "%edi");
+
+	fail("should have died.");
+}
diff --git a/tests/filst/sc-bad-exit.ck b/tests/filst/sc-bad-exit.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1e6fa95e2bb90acab6fab50a2b292b3e73281957
--- /dev/null
+++ b/tests/filst/sc-bad-exit.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-exit) begin
+sc-bad-exit: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-nr-1.c b/tests/filst/sc-bad-nr-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..3a1db35ed1bc3dcb56d7f350b2d0dbe8c107985d
--- /dev/null
+++ b/tests/filst/sc-bad-nr-1.c
@@ -0,0 +1,24 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall-nr.h>
+
+// From "lib/user/syscall.c", but modified to push some zeros on the stack
+// first, to get a better output for the tests if the stack error message
+// remains.
+#define syscall0(NUMBER)                                                            \
+	({                                                                               \
+		int retval;                                                                   \
+		asm volatile("pushl $0; pushl $0; pushl %[number]; int $0x30; addl $4, %%esp" \
+						 : "=a"(retval)                                                   \
+						 : [number] "i"(NUMBER)                                           \
+						 : "memory");                                                     \
+		retval;                                                                       \
+	})
+
+void test_main(void)
+{
+	// Call a syscall that is one larger than the largest syscall number.
+	syscall0(SYS_NUMBER_OF_CALLS);
+	fail("Should have failed.");
+}
diff --git a/tests/filst/sc-bad-nr-1.ck b/tests/filst/sc-bad-nr-1.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a7e4d201d16284483085dc9d47ea3e01dcd41fc7
--- /dev/null
+++ b/tests/filst/sc-bad-nr-1.ck
@@ -0,0 +1,21 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF', <<'EOF']);
+(sc-bad-nr-1) begin
+sc-bad-nr-1: exit(-1)
+EOF
+(sc-bad-nr-1) begin
+Executed an unknown system call!
+Stack top + 0: 21
+Stack top + 1: 0
+sc-bad-nr-1: exit(-1)
+EOF
+(sc-bad-nr-1) begin
+Executed an unknown system call!
+Stack top + 0: 22
+Stack top + 1: 0
+sc-bad-nr-1: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-nr-2.c b/tests/filst/sc-bad-nr-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..3802ef58d239d5867aec79290f9e0374c1a6a9a7
--- /dev/null
+++ b/tests/filst/sc-bad-nr-2.c
@@ -0,0 +1,24 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall-nr.h>
+
+// From "lib/user/syscall.c", but modified to push some zeros on the stack
+// first, to get a better output for the tests if the stack error message
+// remains.
+#define syscall0(NUMBER)                                                            \
+	({                                                                               \
+		int retval;                                                                   \
+		asm volatile("pushl $0; pushl $0; pushl %[number]; int $0x30; addl $4, %%esp" \
+						 : "=a"(retval)                                                   \
+						 : [number] "i"(NUMBER)                                           \
+						 : "memory");                                                     \
+		retval;                                                                       \
+	})
+
+void test_main(void)
+{
+	// Call a syscall that is quite a bit larger than the maximum syscall number.
+	syscall0(1024 * 1024);
+	fail("Should have failed.");
+}
diff --git a/tests/filst/sc-bad-nr-2.ck b/tests/filst/sc-bad-nr-2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6580527e76035f6d0bd6bac383e7772244b0805f
--- /dev/null
+++ b/tests/filst/sc-bad-nr-2.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(sc-bad-nr-2) begin
+sc-bad-nr-2: exit(-1)
+EOF
+(sc-bad-nr-2) begin
+Executed an unknown system call!
+Stack top + 0: 1048576
+Stack top + 1: 0
+sc-bad-nr-2: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-nr-3.c b/tests/filst/sc-bad-nr-3.c
new file mode 100644
index 0000000000000000000000000000000000000000..d5c28129c9b3f15079ff826ee76bf6b1c16d69b3
--- /dev/null
+++ b/tests/filst/sc-bad-nr-3.c
@@ -0,0 +1,24 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall-nr.h>
+
+// From "lib/user/syscall.c", but modified to push some zeros on the stack
+// first, to get a better output for the tests if the stack error message
+// remains.
+#define syscall0(NUMBER)                                                            \
+	({                                                                               \
+		int retval;                                                                   \
+		asm volatile("pushl $0; pushl $0; pushl %[number]; int $0x30; addl $4, %%esp" \
+						 : "=a"(retval)                                                   \
+						 : [number] "i"(NUMBER)                                           \
+						 : "memory");                                                     \
+		retval;                                                                       \
+	})
+
+void test_main(void)
+{
+	// Call a negative syscall.
+	syscall0(-1);
+	fail("Should have failed.");
+}
diff --git a/tests/filst/sc-bad-nr-3.ck b/tests/filst/sc-bad-nr-3.ck
new file mode 100644
index 0000000000000000000000000000000000000000..650f050c4b3f3e12cd11001aec41cc95ed2e4bbb
--- /dev/null
+++ b/tests/filst/sc-bad-nr-3.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(sc-bad-nr-3) begin
+sc-bad-nr-3: exit(-1)
+EOF
+(sc-bad-nr-3) begin
+Executed an unknown system call!
+Stack top + 0: -1
+Stack top + 1: 0
+sc-bad-nr-3: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-bad-write.c b/tests/filst/sc-bad-write.c
new file mode 100644
index 0000000000000000000000000000000000000000..76811f0362254d9e41f83a48adf0e7031f475736
--- /dev/null
+++ b/tests/filst/sc-bad-write.c
@@ -0,0 +1,76 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <syscall-nr.h>
+
+/**
+ * From threads/vaddr.h:
+ */
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+#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). */
+
+static inline void* pg_round_up(const void* va)
+{
+	return (void*) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/**
+ * External symbol which address is the first address after all data in the BSS segment.
+ */
+extern int _end_bss;
+
+/**
+ * String constants used in the asm blocks.
+ */
+const char* WORKS = "WORKS\n";
+const char* FAIL = "FAIL\n";
+
+void test_main(void)
+{
+	// Get the address of the first unmapped page in the process.
+	unsigned page = (unsigned) pg_round_up(&_end_bss);
+
+	// Reserve space for 4 parameters.
+	unsigned base = page - sizeof(int) * 4;
+
+	// Call write() with space for 4 parameters (should be fine).
+	asm volatile(
+		 "movl %%esp, %%edi;"
+		 "movl %0, %%esp;"		 // Set stack pointer to right below page boundary.
+		 "movl %1, (%%esp);"		 // Try to call SYS_WRITE
+		 "movl %2, 4(%%esp);"	 // Write to STDOUT
+		 "movl %3, 8(%%esp);"	 // Load buffer.
+		 "movl $6, 12(%%esp);"	 // Write length of string
+		 "int $0x30;"
+		 "movl %%edi, %%esp;"	// Restore esp.
+		 :
+		 : "r"(base), "i"(SYS_WRITE), "i"(STDOUT_FILENO), "r"(WORKS)
+		 : "%eax", "%edi");
+
+	// Reserve space for 3 parameters (write requires 4).
+	base = page - sizeof(int) * 3;
+
+	// Call write() with space for 3 parameters (the kernel should kill us for doing
+	// this).
+	asm volatile(
+		 "movl %%esp, %%edi;"
+		 "movl %0, %%esp;"		// Set stack pointer to right below page boundary.
+		 "movl %1, (%%esp);"		// Try to call SYS_WRITE
+		 "movl %2, 4(%%esp);"	// Write to STDOUT
+		 "movl %3, 8(%%esp);"	// Load buffer.
+		 //"movl $5, 12(%%esp);" // Can not write the last parameter as we would get a
+		 // pagefault.
+		 "int $0x30;"
+		 "movl %%edi, %%esp;"	// Restore esp in case we do not crash (as we should).
+		 :
+		 : "r"(base), "i"(SYS_WRITE), "i"(STDOUT_FILENO), "r"(FAIL)
+		 : "%eax", "%edi");
+
+	fail("should have died.");
+}
diff --git a/tests/filst/sc-bad-write.ck b/tests/filst/sc-bad-write.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b23175a55586500594655f326dd89db93702f523
--- /dev/null
+++ b/tests/filst/sc-bad-write.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-bad-write) begin
+WORKS
+sc-bad-write: exit(-1)
+EOF
+pass;
diff --git a/tests/filst/sc-write-buf.c b/tests/filst/sc-write-buf.c
new file mode 100644
index 0000000000000000000000000000000000000000..5081c64ef6cb5910ebcb467a9a42e7ee628bdb25
--- /dev/null
+++ b/tests/filst/sc-write-buf.c
@@ -0,0 +1,59 @@
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall-nr.h>
+
+/**
+ * Thanks to Herman Appelgren for suggesting this test case.
+ */
+
+/**
+ * From threads/vaddr.h:
+ */
+#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))
+
+#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). */
+
+static inline void* pg_round_up(const void* va)
+{
+	return (void*) (((uintptr_t) va + PGSIZE - 1) & ~PGMASK);
+}
+
+/**
+ * External symbol which address is the first address after all data in the BSS segment.
+ */
+extern int _end_bss;
+
+void test_main(void)
+{
+	// Create a file to write to.
+	create("test", 50);
+
+	// Open the file.
+	int fd = open("test");
+	if (fd < 0)
+		fail("could not open file.");
+
+	// Get the address of the first unmapped page in the process. Addresses from
+	// here and onwards are not mapped to any physical address.
+	char* message = pg_round_up(&_end_bss);
+
+	// Go down a few bytes so that we can store our message.
+	message -= 10;
+
+	// Copy the string into the end of the last page. It will fit the end of the
+	// page with some room to spare.
+	strlcpy(message, "Hello", 6);
+
+	// Call write with a too large address. This should fail as the last few
+	// addresses are beyond the mapped region in memory.
+	write(fd, message, 20);
+
+	fail("should have died.");
+}
diff --git a/tests/filst/sc-write-buf.ck b/tests/filst/sc-write-buf.ck
new file mode 100644
index 0000000000000000000000000000000000000000..dc1ac05fe1de470ab51833468dfdd7a32e6b52f2
--- /dev/null
+++ b/tests/filst/sc-write-buf.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-write-buf) begin
+sc-write-buf: exit(-1)
+EOF
+pass;
diff --git a/tests/internal/list.c b/tests/internal/list.c
new file mode 100644
index 0000000000000000000000000000000000000000..c4841ae3c15b9aa5f696d5b5712f502c73c77b9b
--- /dev/null
+++ b/tests/internal/list.c
@@ -0,0 +1,157 @@
+/* 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 "threads/test.h"
+
+#include <debug.h>
+#include <list.h>
+#include <random.h>
+#include <stdio.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/tests/internal/stdio.c b/tests/internal/stdio.c
new file mode 100644
index 0000000000000000000000000000000000000000..6f7641cf433608b10420888e9d04b666b76a8427
--- /dev/null
+++ b/tests/internal/stdio.c
@@ -0,0 +1,206 @@
+/* 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 "threads/test.h"
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.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/tests/internal/stdlib.c b/tests/internal/stdlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..396e8cbc8ce08af3da5a62d50c54b52e2f7bf72b
--- /dev/null
+++ b/tests/internal/stdlib.c
@@ -0,0 +1,104 @@
+/* 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 "threads/test.h"
+
+#include <debug.h>
+#include <limits.h>
+#include <random.h>
+#include <stdio.h>
+#include <stdlib.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/tests/klaar/Make.tests b/tests/klaar/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..390e52c2abf11cc61d988975541df2ed8224c4bc
--- /dev/null
+++ b/tests/klaar/Make.tests
@@ -0,0 +1,38 @@
+# -*- 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 pfs)
+
+tests/klaar_PROGS = $(tests/klaar_TESTS) $(addprefix \
+tests/klaar/,child-simple pfs-reader pfs-writer)
+
+# read-bad-buf
+tests/klaar/read-bad-buf_SRC = tests/klaar/read-bad-buf.c tests/main.c
+tests/klaar/read-bad-buf_PUTFILES += tests/klaar/sample.txt
+
+# low-mem
+tests/klaar/low-mem_SRC = tests/klaar/low-mem.c tests/main.c
+tests/klaar/child-simple_SRC = tests/klaar/child-simple.c
+tests/klaar/low-mem_PUTFILES += tests/klaar/child-simple
+
+# exec-corrupt
+tests/klaar/exec-corrupt_SRC += tests/klaar/exec-corrupt.c tests/main.c
+tests/klaar/exec-corrupt_PUTFILES += tests/klaar/corrupt-elf
+
+# pfs
+tests/klaar/pfs_SRC = tests/klaar/pfs.c
+tests/klaar/pfs-reader_SRC = tests/klaar/pfs-reader.c
+tests/klaar/pfs-writer_SRC = tests/klaar/pfs-writer.c
+tests/klaar/pfs_PUTFILES += tests/klaar/pfs-reader
+tests/klaar/pfs_PUTFILES += tests/klaar/pfs-writer
+tests/klaar/pfs_ARGS = 10 5
+
+$(foreach prog,$(tests/klaar_PROGS),$(eval $(prog)_SRC += tests/lib.c))
+
+tests/klaar/low-mem.output: KERNELFLAGS = -tcl=3
+
+tests/klaar/pfs.output: TIMEOUT = 300
+tests/klaar/pfs.output: KERNELFLAGS = -S
diff --git a/tests/klaar/child-simple.c b/tests/klaar/child-simple.c
new file mode 100644
index 0000000000000000000000000000000000000000..fa26a1d31b65f696d76841996d594d1511573be5
--- /dev/null
+++ b/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 "tests/lib.h"
+
+#include <stdio.h>
+
+const char* test_name = "child-simple";
+
+int main(void)
+{
+	msg("run");
+	return 81;
+}
diff --git a/tests/klaar/corrupt-elf b/tests/klaar/corrupt-elf
new file mode 100644
index 0000000000000000000000000000000000000000..456421bc138c6c1abc857bf1cf52cf50b514999e
Binary files /dev/null and b/tests/klaar/corrupt-elf differ
diff --git a/tests/klaar/exec-corrupt.c b/tests/klaar/exec-corrupt.c
new file mode 100644
index 0000000000000000000000000000000000000000..e55da99ae04844cc8bad9b5b6e18744dace5845e
--- /dev/null
+++ b/tests/klaar/exec-corrupt.c
@@ -0,0 +1,14 @@
+/*
+  Try to load a corrupt executable.
+  (klaar@ida)
+*/
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("exec(\"corrupt-elf\"): %d", exec("corrupt-elf"));
+}
diff --git a/tests/klaar/exec-corrupt.ck b/tests/klaar/exec-corrupt.ck
new file mode 100644
index 0000000000000000000000000000000000000000..e323b8df5a6cd86cfabf4cbc72d7be2c7a982bee
--- /dev/null
+++ b/tests/klaar/exec-corrupt.ck
@@ -0,0 +1,45 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF', <<'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
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+exec-corrupt: exit(0)
+corrupt-elf: exit(-1)
+EOF
+(exec-corrupt) begin
+load: corrupt-elf: error loading executable
+(exec-corrupt) exec("corrupt-elf"): -1
+(exec-corrupt) end
+corrupt-elf: exit(-1)
+exec-corrupt: exit(0)
+EOF
+pass;
diff --git a/tests/klaar/low-mem.c b/tests/klaar/low-mem.c
new file mode 100644
index 0000000000000000000000000000000000000000..d7639c54b776f4abc3eee0c64d9faa00ecde9e39
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("exec(\"child-simple\"): %d", exec("child-simple"));
+}
diff --git a/tests/klaar/low-mem.ck b/tests/klaar/low-mem.ck
new file mode 100644
index 0000000000000000000000000000000000000000..f128c30b01dfbc74101a5b0ab10017c3a9fc7b4b
--- /dev/null
+++ b/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/tests/klaar/pfs-reader.c b/tests/klaar/pfs-reader.c
new file mode 100644
index 0000000000000000000000000000000000000000..d84de7f482e600315309fa3322e581e02b94f96a
--- /dev/null
+++ b/tests/klaar/pfs-reader.c
@@ -0,0 +1,41 @@
+/* Part of pfs.c suite.
+
+	Reads from the file and checks consistency.
+	The buffer should all contain the same character!!
+ */
+
+#include "../lib.h"
+#include "pfs.h"
+
+#include <stdio.h>
+#include <syscall.h>
+
+static bool check_consistency(char* buf, int size)
+{
+	for (int i = 1; i < size; ++i) {
+		if (buf[0] != buf[i]) {
+			/* Ooops, inconsistency */
+			return false;
+		}
+	}
+	return true;
+}
+
+char buffer[BIG];
+
+int main(int argc UNUSED, char* argv[])
+{
+	test_name = argv[0];
+	quiet = true;
+
+	for (int i = 0; i < TIMES; ++i) {
+		int id = open("file.1");
+		CHECK(id > 1, "open \"file.1\"");
+		int bytes = read(id, buffer, BIG);
+		CHECK(bytes == BIG, "read \"file.1\"");
+		close(id);
+
+		CHECK(check_consistency(buffer, BIG), "inconsistency");
+	}
+	return 0;
+}
diff --git a/tests/klaar/pfs-writer.c b/tests/klaar/pfs-writer.c
new file mode 100644
index 0000000000000000000000000000000000000000..659f4a1656ff1f926790cdceb0affd65ebbbb420
--- /dev/null
+++ b/tests/klaar/pfs-writer.c
@@ -0,0 +1,50 @@
+/* Part of pfs.c suite.
+
+	Write on the disk. Each time the buffer is filled with same
+	character. Different character every time!
+ */
+
+#include "../lib.h"
+#include "pfs.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+static void fill_buffer(char* buf, int size, char c)
+{
+	for (int i = 0; i < size; i++) {
+		buf[i] = c;
+	}
+}
+
+char buffer[BIG];
+
+int main(int argc, char* argv[])
+{
+	test_name = argv[0];
+	quiet = true;
+
+	if (argc != 3 || strlen(argv[1]) != 1 || strlen(argv[2]) != 1)
+		return 1;
+
+	char start = argv[1][0];
+	char end = argv[2][0];
+
+	char c = start;
+	for (int i = 0; i < TIMES; ++i) {
+		fill_buffer(buffer, BIG, c);
+
+		int id = open("file.1");
+		CHECK(id > 1, "open \"file.1\"");
+		int bytes = write(id, buffer, BIG);
+		CHECK(bytes == BIG, "write \"file.1\"");
+		close(id);
+
+		c = c + 1;
+
+		if (c > end)
+			c = start;
+	}
+	return 0;
+}
diff --git a/tests/klaar/pfs.c b/tests/klaar/pfs.c
new file mode 100644
index 0000000000000000000000000000000000000000..a70a193033ce8410e937ffca79bf5c84f7e0bf8c
--- /dev/null
+++ b/tests/klaar/pfs.c
@@ -0,0 +1,86 @@
+/* klaar@ida (convert to built-in pintos test)
+
+	Tests that read and write are properly synchronized. Also stresses
+	the filesystem somewhat.
+
+	pfs_reader and pfs_writer are needed child processes.
+*/
+
+#include "pfs.h"
+
+#include "../lib.h"
+
+#include <random.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+static void swap(char* a, char* b)
+{
+	char c = *a;
+	*a = *b;
+	*b = c;
+}
+
+static void start_rw(int rn, int wn)
+{
+	int rpid = -1;
+	int wpid = -1;
+
+	if (rn > 0) {
+		rpid = exec("pfs-reader");
+		msg("exec(\"pfs-reader\"): %d", rpid);
+	}
+
+	if (wn > 0) {
+		int range = '~' - '!' + 1;	  // ascii 32 - 126
+		char start = '!' + random_ulong() % range;
+		char end = '!' + random_ulong() % range;
+
+		if (end < start)
+			swap(&start, &end);
+
+		const int CMDSIZE = 64;
+		char cmd[CMDSIZE];
+		snprintf(cmd, CMDSIZE, "pfs-writer %c %c", start, end);
+
+		wpid = exec(cmd);
+		msg("exec(\"%s\"): %d", cmd, wpid);
+	}
+
+	if (rn > 0 || wn > 0)
+		start_rw(rn - 1, wn - 1);
+
+	if (rpid != -1)
+		CHECK(wait(rpid) != -1, "wait pfs-reader %d", rpid);
+
+	if (wpid != -1)
+		CHECK(wait(wpid) != -1, "wait pfs-writer %d", wpid);
+}
+
+int main(int argc, char* argv[])
+{
+	if (argc != 3)
+		fail("usage: pfs START END");
+
+	quiet = true;
+	test_name = argv[0];
+	int num_reader = atoi(argv[1]);
+	int num_writer = atoi(argv[2]);
+
+	msg("begin");
+
+	CHECK(create("file.1", BIG), "create \"file.1\"");
+	CHECK(create("messages", TIMES), "create \"messages\"");
+
+	random_init(0);
+
+	start_rw(num_reader, num_writer);
+
+	msg("end");
+
+	// Tell the system we're entirely done. Otherwise the test accepts
+	// that this process is killed for some reason (e.g. invalid syscall).
+	printf("pfs done\n");
+	return 0;
+}
diff --git a/tests/klaar/pfs.ck b/tests/klaar/pfs.ck
new file mode 100644
index 0000000000000000000000000000000000000000..83786099dfccbc6000ded9da38aa1c6e8982dc22
--- /dev/null
+++ b/tests/klaar/pfs.ck
@@ -0,0 +1,8 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+pfs done
+EOF
+pass;
diff --git a/tests/klaar/pfs.h b/tests/klaar/pfs.h
new file mode 100644
index 0000000000000000000000000000000000000000..144754633ec5af2122b1aaa6622c8921ec2a8d83
--- /dev/null
+++ b/tests/klaar/pfs.h
@@ -0,0 +1,2 @@
+#define BIG	  3000
+#define TIMES 200
diff --git a/tests/klaar/read-bad-buf.c b/tests/klaar/read-bad-buf.c
new file mode 100644
index 0000000000000000000000000000000000000000..6c50709c89bab9994e6823c593e228121484c5e2
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/klaar/read-bad-buf.ck b/tests/klaar/read-bad-buf.ck
new file mode 100644
index 0000000000000000000000000000000000000000..22adc509a6f3e85842db6457d9211c879b3ff362
--- /dev/null
+++ b/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/tests/klaar/sample.txt b/tests/klaar/sample.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5050fec989ccb23041ed7010d2973f834fd35123
--- /dev/null
+++ b/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/tests/lib.c b/tests/lib.c
new file mode 100644
index 0000000000000000000000000000000000000000..7cef0fcf287495bb132a3a39c38e07c23710b22e
--- /dev/null
+++ b/tests/lib.c
@@ -0,0 +1,201 @@
+#include "tests/lib.h"
+
+#include <random.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+
+// Note: test_name is overridden in some tests, and not in others.
+__attribute__((weak)) 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/tests/lib.h b/tests/lib.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2a8337bcc101ed36667d537a3faf1367306f221
--- /dev/null
+++ b/tests/lib.h
@@ -0,0 +1,52 @@
+#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/tests/lib.pm b/tests/lib.pm
new file mode 100644
index 0000000000000000000000000000000000000000..bc37ae5245f043452af7718d1c4b0336a302801f
--- /dev/null
+++ b/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/tests/main.c b/tests/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..3e607a1f473eb84d933e80aab5356471d34c9d73
--- /dev/null
+++ b/tests/main.c
@@ -0,0 +1,16 @@
+#include "tests/main.h"
+
+#include "tests/lib.h"
+
+#include <random.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/tests/main.h b/tests/main.h
new file mode 100644
index 0000000000000000000000000000000000000000..be2e9c2d0e74c0a4bd82668d5cecbbc30f072120
--- /dev/null
+++ b/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/tests/make-grade b/tests/make-grade
new file mode 100644
index 0000000000000000000000000000000000000000..a3faa0ebd560ac1f5af6b1180a50f489125860ca
--- /dev/null
+++ b/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/tests/random.pm b/tests/random.pm
new file mode 100644
index 0000000000000000000000000000000000000000..be008ffc1adcc102f5907b236fc99b839d59d6a6
--- /dev/null
+++ b/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/tests/tests.pm b/tests/tests.pm
new file mode 100644
index 0000000000000000000000000000000000000000..4599cb98fe49d7627173d182f48ce2ef072f03ab
--- /dev/null
+++ b/tests/tests.pm
@@ -0,0 +1,625 @@
+use strict;
+use warnings;
+use tests::Algorithm::Diff;
+use File::Temp 'tempfile';
+use Fcntl qw(SEEK_SET SEEK_CUR);
+
+sub fail;
+sub pass;
+
+die if @ARGV != 2;
+our ($test, $src_dir) = @ARGV;
+
+my ($msg_file) = tempfile ();
+select ($msg_file);
+
+our (@prereq_tests) = ();
+if ($test =~ /^(.*)-persistence$/) {
+    push (@prereq_tests, $1);
+}
+for my $prereq_test (@prereq_tests) {
+    my (@result) = read_text_file ("$prereq_test.result");
+    fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS';
+}
+
+
+# Generic testing.
+
+sub check_expected {
+    my ($expected) = pop @_;
+    my (@options) = @_;
+    my (@output) = read_text_file ("$test.output");
+    common_checks ("run", @output);
+    compare_output ("run", @options, \@output, $expected);
+}
+
+sub common_checks {
+    my ($run, @output) = @_;
+
+    fail "\u$run produced no output at all\n" if @output == 0;
+
+    check_for_panic ($run, @output);
+    check_for_keyword ($run, "FAIL", @output);
+    check_for_triple_fault ($run, @output);
+    check_for_keyword ($run, "TIMEOUT", @output);
+
+    fail "\u$run didn't start up properly: no \"Pintos booting\" message\n"
+      if !grep (/Pintos booting with.*kB RAM\.\.\./, @output);
+    fail "\u$run didn't start up properly: no \"Boot complete\" message\n"
+      if !grep (/Boot complete/, @output);
+    fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n"
+      if !grep (/Timer: \d+ ticks/, @output);
+    fail "\u$run didn't shut down properly: no \"Powering off\" message\n"
+      if !grep (/Powering off/, @output);
+}
+
+sub check_for_panic {
+    my ($run, @output) = @_;
+
+    my ($panic) = grep (/PANIC/, @output);
+    return unless defined $panic;
+
+    print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")),
+      "\n";
+
+    my (@stack_line) = grep (/Call stack:/, @output);
+    if (@stack_line != 0) {
+	my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/;
+
+	# Find a user program to translate user virtual addresses.
+	my ($userprog) = "";
+	$userprog = "$test"
+	  if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test;
+
+	# Get and print the backtrace.
+	my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`);
+	print "Call stack:$addrs\n";
+	print "Translation of call stack:\n";
+	print $trace;
+
+	# Print disclaimer.
+	if ($userprog ne '' && index ($trace, $userprog) >= 0) {
+	    print <<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.
+	$name =~ s%^(/|\./|\.\./)*%%;	# Strip leading "/", "./", "../".
+	$name = '' if $name eq '.' || $name eq '..';
+	if (exists $content{$name}) {
+	    fail "$archive: contains multiple entries for $name\n";
+	}
+	if ($typeflag eq '5') {
+	    $content{$name} = 'directory' if $name ne '';
+	} else {
+	    fail "$archive: contains file with empty name\n" if $name eq '';
+	    my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR);
+	    $content{$name} = [$archive, $position, $size];
+	    sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR);
+	}
+    }
+    close (ARCHIVE);
+    return %content;
+}
+
+# Utilities.
+
+sub fail {
+    finish ("FAIL", @_);
+}
+
+sub pass {
+    finish ("PASS", @_);
+}
+
+sub finish {
+    my ($verdict, @messages) = @_;
+
+    seek ($msg_file, 0, 0);
+    push (@messages, <$msg_file>);
+    close ($msg_file);
+    chomp (@messages);
+
+    my ($result_fn) = "$test.result";
+    open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n";
+    print RESULT "$verdict\n";
+    print RESULT "$_\n" foreach @messages;
+    close (RESULT);
+
+    if ($verdict eq 'PASS') {
+	print STDOUT "pass $test\n";
+    } else {
+	print STDOUT "FAIL $test\n";
+    }
+    print STDOUT "$_\n" foreach @messages;
+
+    exit 0;
+}
+
+sub read_text_file {
+    my ($file_name) = @_;
+    open (FILE, '<', $file_name) or die "$file_name: open: $!\n";
+    my (@content) = <FILE>;
+    chomp (@content);
+    close (FILE);
+    return @content;
+}
+
+1;
diff --git a/tests/threads/Grading b/tests/threads/Grading
new file mode 100644
index 0000000000000000000000000000000000000000..c9be35f379dfc8241b489eb28966617629cdbd76
--- /dev/null
+++ b/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/tests/threads/Make.tests b/tests/threads/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..c25af2da66b1f50814af25e51ed8670316af0274
--- /dev/null
+++ b/tests/threads/Make.tests
@@ -0,0 +1,57 @@
+# -*- makefile -*-
+
+# Test names.
+# tests/threads_TESTS = $(addprefix tests/threads/,alarm-single		\
+# alarm-multiple alarm-simultaneous alarm-priority alarm-zero		\
+# alarm-negative priority-change priority-donate-one			\
+# priority-donate-multiple priority-donate-multiple2			\
+# priority-donate-nest priority-donate-sema priority-donate-lower		\
+# priority-fifo priority-preempt priority-sema priority-condvar		\
+# priority-donate-chain                                                   \
+# mlfqs-load-1 mlfqs-load-60 mlfqs-load-avg mlfqs-recent-1 mlfqs-fair-2	\
+# mlfqs-fair-20 mlfqs-nice-2 mlfqs-nice-10 mlfqs-block)
+# 
+
+tests/threads_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
+
+# 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/tests/threads/Rubric.alarm b/tests/threads/Rubric.alarm
new file mode 100644
index 0000000000000000000000000000000000000000..61abe85e348350b37082760997b8936dce55bc66
--- /dev/null
+++ b/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/tests/threads/Rubric.mlfqs b/tests/threads/Rubric.mlfqs
new file mode 100644
index 0000000000000000000000000000000000000000..f2600916205eccba338346f243b1c2eb9939bf71
--- /dev/null
+++ b/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/tests/threads/Rubric.priority b/tests/threads/Rubric.priority
new file mode 100644
index 0000000000000000000000000000000000000000..652bc99eff1f34d1c40e34259af31291d4069d85
--- /dev/null
+++ b/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/tests/threads/alarm-multiple.ck b/tests/threads/alarm-multiple.ck
new file mode 100644
index 0000000000000000000000000000000000000000..fd83bcd4087f5edacbf2bf81b82e83d49c9c049b
--- /dev/null
+++ b/tests/threads/alarm-multiple.ck
@@ -0,0 +1,4 @@
+# -*- perl -*-
+use tests::tests;
+use tests::threads::alarm;
+check_alarm (7);
diff --git a/tests/threads/alarm-negative.c b/tests/threads/alarm-negative.c
new file mode 100644
index 0000000000000000000000000000000000000000..1730a5648d7b6a377da52d4602173223ee0e4d15
--- /dev/null
+++ b/tests/threads/alarm-negative.c
@@ -0,0 +1,15 @@
+/* Tests timer_sleep(-100).  Only requirement is that it not crash. */
+
+#include "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.h>
+
+void test_alarm_negative(void)
+{
+	timer_sleep(-100);
+	pass();
+}
diff --git a/tests/threads/alarm-negative.ck b/tests/threads/alarm-negative.ck
new file mode 100644
index 0000000000000000000000000000000000000000..0d2bab07aefecada5942ae1e3ca9e2d4a993820d
--- /dev/null
+++ b/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/tests/threads/alarm-priority.c b/tests/threads/alarm-priority.c
new file mode 100644
index 0000000000000000000000000000000000000000..4d07705cbdebcaa12efc562d9e1b6d061db09c5a
--- /dev/null
+++ b/tests/threads/alarm-priority.c
@@ -0,0 +1,54 @@
+/* Checks that when the alarm clock wakes up threads, the
+	higher-priority threads run first. */
+
+#include "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/alarm-priority.ck b/tests/threads/alarm-priority.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b57c78b38163c97d84ba82e73058df1876e207f0
--- /dev/null
+++ b/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/tests/threads/alarm-simultaneous.c b/tests/threads/alarm-simultaneous.c
new file mode 100644
index 0000000000000000000000000000000000000000..7343fc35996678ded4410e61617d1bf5dad39b8d
--- /dev/null
+++ b/tests/threads/alarm-simultaneous.c
@@ -0,0 +1,91 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/alarm-simultaneous.ck b/tests/threads/alarm-simultaneous.ck
new file mode 100644
index 0000000000000000000000000000000000000000..406b8b0414defc8544b6d29f24ba22e5a99eb123
--- /dev/null
+++ b/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/tests/threads/alarm-single.ck b/tests/threads/alarm-single.ck
new file mode 100644
index 0000000000000000000000000000000000000000..31215dffe754d3b5d8ab7b48aa00f7e6e489e655
--- /dev/null
+++ b/tests/threads/alarm-single.ck
@@ -0,0 +1,4 @@
+# -*- perl -*-
+use tests::tests;
+use tests::threads::alarm;
+check_alarm (1);
diff --git a/tests/threads/alarm-wait.c b/tests/threads/alarm-wait.c
new file mode 100644
index 0000000000000000000000000000000000000000..2785458f5a1fc1c2790ad933d79a26ef37aa071d
--- /dev/null
+++ b/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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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;
+
+/* The way the tests are built make sure that this isn't a problem */
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+		snprintf(name, sizeof name, "thread %d", i);
+#pragma GCC diagnostic pop
+		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/tests/threads/alarm-zero.c b/tests/threads/alarm-zero.c
new file mode 100644
index 0000000000000000000000000000000000000000..4f764dce8db675b4af708d15e92b29abf65e7e60
--- /dev/null
+++ b/tests/threads/alarm-zero.c
@@ -0,0 +1,15 @@
+/* Tests timer_sleep(0), which should return immediately. */
+
+#include "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.h>
+
+void test_alarm_zero(void)
+{
+	timer_sleep(0);
+	pass();
+}
diff --git a/tests/threads/alarm-zero.ck b/tests/threads/alarm-zero.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a6b1a3cd77920604c6c9ff41854be7a1b347abfa
--- /dev/null
+++ b/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/tests/threads/alarm.pm b/tests/threads/alarm.pm
new file mode 100644
index 0000000000000000000000000000000000000000..84b3b7f741de84841614b22b956876179f738dff
--- /dev/null
+++ b/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/tests/threads/mlfqs-block.c b/tests/threads/mlfqs-block.c
new file mode 100644
index 0000000000000000000000000000000000000000..26b7c9ccf63d45aeb5187897655780f42d143c48
--- /dev/null
+++ b/tests/threads/mlfqs-block.c
@@ -0,0 +1,61 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/mlfqs-block.ck b/tests/threads/mlfqs-block.ck
new file mode 100644
index 0000000000000000000000000000000000000000..8833a3affc0069b4c34fcd8a92f1bbdb76fae989
--- /dev/null
+++ b/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/tests/threads/mlfqs-fair-2.ck b/tests/threads/mlfqs-fair-2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..5b19ff1391301a8174c2fd0f4bb268595c76223b
--- /dev/null
+++ b/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/tests/threads/mlfqs-fair-20.ck b/tests/threads/mlfqs-fair-20.ck
new file mode 100644
index 0000000000000000000000000000000000000000..bb4d051e7b4a3b0b48a81fa91bfc0fb3848745e1
--- /dev/null
+++ b/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/tests/threads/mlfqs-fair.c b/tests/threads/mlfqs-fair.c
new file mode 100644
index 0000000000000000000000000000000000000000..61dbb8d10583e212e33475eb10d63edfa3dc085e
--- /dev/null
+++ b/tests/threads/mlfqs-fair.c
@@ -0,0 +1,116 @@
+/* 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 "devices/timer.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 <inttypes.h>
+#include <stdio.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/tests/threads/mlfqs-load-1.c b/tests/threads/mlfqs-load-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..44c69926e0a9f89714d26ff7ad461d2e4b332898
--- /dev/null
+++ b/tests/threads/mlfqs-load-1.c
@@ -0,0 +1,61 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/mlfqs-load-1.ck b/tests/threads/mlfqs-load-1.ck
new file mode 100644
index 0000000000000000000000000000000000000000..faf0ffac0fcdf25f1ad93654b254ca04d8079fd7
--- /dev/null
+++ b/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/tests/threads/mlfqs-load-60.c b/tests/threads/mlfqs-load-60.c
new file mode 100644
index 0000000000000000000000000000000000000000..0903142b201b8278df949fb07a7c67251994862a
--- /dev/null
+++ b/tests/threads/mlfqs-load-60.c
@@ -0,0 +1,152 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/mlfqs-load-60.ck b/tests/threads/mlfqs-load-60.ck
new file mode 100644
index 0000000000000000000000000000000000000000..cb69220024ef7973a3218623a372f2e1ab3477e9
--- /dev/null
+++ b/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/tests/threads/mlfqs-load-avg.c b/tests/threads/mlfqs-load-avg.c
new file mode 100644
index 0000000000000000000000000000000000000000..7c22642051696bcb4a887947ac21aa597abf90bd
--- /dev/null
+++ b/tests/threads/mlfqs-load-avg.c
@@ -0,0 +1,164 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/mlfqs-load-avg.ck b/tests/threads/mlfqs-load-avg.ck
new file mode 100644
index 0000000000000000000000000000000000000000..2254d05467ee7a8bce10994ff10f98a91919f7f4
--- /dev/null
+++ b/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/tests/threads/mlfqs-nice-10.ck b/tests/threads/mlfqs-nice-10.ck
new file mode 100644
index 0000000000000000000000000000000000000000..53e0abe9f16337ba81f14caf127ef6a9a8f5a7e3
--- /dev/null
+++ b/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/tests/threads/mlfqs-nice-2.ck b/tests/threads/mlfqs-nice-2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ada366ba53578a228fc31bd6d2053ddce80f30f4
--- /dev/null
+++ b/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/tests/threads/mlfqs-recent-1.c b/tests/threads/mlfqs-recent-1.c
new file mode 100644
index 0000000000000000000000000000000000000000..54dbea0bf5d4448aef74969330bc883edcca5e7b
--- /dev/null
+++ b/tests/threads/mlfqs-recent-1.c
@@ -0,0 +1,141 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/mlfqs-recent-1.ck b/tests/threads/mlfqs-recent-1.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a2ba44d375987e7d3504adce66f1dd78efbfddf6
--- /dev/null
+++ b/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/tests/threads/mlfqs.pm b/tests/threads/mlfqs.pm
new file mode 100644
index 0000000000000000000000000000000000000000..184ac16a0a1c2c45304ca7580145eb067e3416a9
--- /dev/null
+++ b/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/tests/threads/priority-change.c b/tests/threads/priority-change.c
new file mode 100644
index 0000000000000000000000000000000000000000..c7f1937fedd9195f118ad89ef45900bdbcb3dd0c
--- /dev/null
+++ b/tests/threads/priority-change.c
@@ -0,0 +1,30 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-change.ck b/tests/threads/priority-change.ck
new file mode 100644
index 0000000000000000000000000000000000000000..f4d9b2f34f1fdc6a3910f83b43f33ca826f1ae20
--- /dev/null
+++ b/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/tests/threads/priority-condvar.c b/tests/threads/priority-condvar.c
new file mode 100644
index 0000000000000000000000000000000000000000..28874be913d8d9792e2083660ee1272e0c9f1b2c
--- /dev/null
+++ b/tests/threads/priority-condvar.c
@@ -0,0 +1,50 @@
+/* Tests that cond_signal() wakes up the highest-priority thread
+	waiting in cond_wait(). */
+
+#include "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-condvar.ck b/tests/threads/priority-condvar.ck
new file mode 100644
index 0000000000000000000000000000000000000000..195c1ab4753cd22f92a86eb6291e10cb9216eca7
--- /dev/null
+++ b/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/tests/threads/priority-donate-chain.c b/tests/threads/priority-donate-chain.c
new file mode 100644
index 0000000000000000000000000000000000000000..81293f405c65116f72e29e5ae62581b04d455fcd
--- /dev/null
+++ b/tests/threads/priority-donate-chain.c
@@ -0,0 +1,110 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-chain.ck b/tests/threads/priority-donate-chain.ck
new file mode 100644
index 0000000000000000000000000000000000000000..213e64968c83b48c27678f5076d84844e24bd61a
--- /dev/null
+++ b/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/tests/threads/priority-donate-lower.c b/tests/threads/priority-donate-lower.c
new file mode 100644
index 0000000000000000000000000000000000000000..9f0f0000b2f8a8719d2d4df6047a39beb53d7546
--- /dev/null
+++ b/tests/threads/priority-donate-lower.c
@@ -0,0 +1,53 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-lower.ck b/tests/threads/priority-donate-lower.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c9bb61b170e43ed6ebaddd7905334b837680f643
--- /dev/null
+++ b/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/tests/threads/priority-donate-multiple.c b/tests/threads/priority-donate-multiple.c
new file mode 100644
index 0000000000000000000000000000000000000000..b2cb84dfe9282c2c70a6e51cdb4b1c9bdcb9d855
--- /dev/null
+++ b/tests/threads/priority-donate-multiple.c
@@ -0,0 +1,79 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-multiple.ck b/tests/threads/priority-donate-multiple.ck
new file mode 100644
index 0000000000000000000000000000000000000000..0afd20bd1397fbd1aac7b84b9eccba48ae9e42c9
--- /dev/null
+++ b/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/tests/threads/priority-donate-multiple2.c b/tests/threads/priority-donate-multiple2.c
new file mode 100644
index 0000000000000000000000000000000000000000..e0b9ad2a8f804268b51c829059163f26406fe460
--- /dev/null
+++ b/tests/threads/priority-donate-multiple2.c
@@ -0,0 +1,91 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-multiple2.ck b/tests/threads/priority-donate-multiple2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b23533ae938b3832767c721fbc8d7e523cc9165e
--- /dev/null
+++ b/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/tests/threads/priority-donate-nest.c b/tests/threads/priority-donate-nest.c
new file mode 100644
index 0000000000000000000000000000000000000000..1eb4fa3bcaac430b06c89e92b9f779a040985c55
--- /dev/null
+++ b/tests/threads/priority-donate-nest.c
@@ -0,0 +1,95 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-nest.ck b/tests/threads/priority-donate-nest.ck
new file mode 100644
index 0000000000000000000000000000000000000000..923460ed14a7d20ab84ffa2fdcd60bda110e7cc4
--- /dev/null
+++ b/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/tests/threads/priority-donate-one.c b/tests/threads/priority-donate-one.c
new file mode 100644
index 0000000000000000000000000000000000000000..f30925f03ff50cbad49b0d014cc6ddb6724f72b8
--- /dev/null
+++ b/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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-one.ck b/tests/threads/priority-donate-one.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b7c8e6fe7f599bad767af5225748fd9baf7aca00
--- /dev/null
+++ b/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/tests/threads/priority-donate-sema.c b/tests/threads/priority-donate-sema.c
new file mode 100644
index 0000000000000000000000000000000000000000..747ead511ef29ff1ac8036f0e189b8af7c30ac54
--- /dev/null
+++ b/tests/threads/priority-donate-sema.c
@@ -0,0 +1,78 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-donate-sema.ck b/tests/threads/priority-donate-sema.ck
new file mode 100644
index 0000000000000000000000000000000000000000..92b8d07d7b4fdfa38defe8790873711ffa4b4a04
--- /dev/null
+++ b/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/tests/threads/priority-fifo.c b/tests/threads/priority-fifo.c
new file mode 100644
index 0000000000000000000000000000000000000000..e4271f17ba338ccc72872127e436f44b2f48da6b
--- /dev/null
+++ b/tests/threads/priority-fifo.c
@@ -0,0 +1,95 @@
+/* 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 "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-fifo.ck b/tests/threads/priority-fifo.ck
new file mode 100644
index 0000000000000000000000000000000000000000..11f1dd3e31b305be45b61643a42a0d742abd688a
--- /dev/null
+++ b/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/tests/threads/priority-preempt.c b/tests/threads/priority-preempt.c
new file mode 100644
index 0000000000000000000000000000000000000000..64fe9508a508a1003f3463db3306542a483c8967
--- /dev/null
+++ b/tests/threads/priority-preempt.c
@@ -0,0 +1,39 @@
+/* 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 "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-preempt.ck b/tests/threads/priority-preempt.ck
new file mode 100644
index 0000000000000000000000000000000000000000..43a26ee66c3ac2e2277ae7faa1053b72e7ace63e
--- /dev/null
+++ b/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/tests/threads/priority-sema.c b/tests/threads/priority-sema.c
new file mode 100644
index 0000000000000000000000000000000000000000..a48b6ca8f37e1cc738570570e2f6e218f9883b60
--- /dev/null
+++ b/tests/threads/priority-sema.c
@@ -0,0 +1,42 @@
+/* Tests that the highest-priority thread waiting on a semaphore
+	is the first to wake up. */
+
+#include "devices/timer.h"
+#include "tests/threads/tests.h"
+#include "threads/init.h"
+#include "threads/malloc.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+#include <stdio.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/tests/threads/priority-sema.ck b/tests/threads/priority-sema.ck
new file mode 100644
index 0000000000000000000000000000000000000000..559988d7346abc29958909573a577611323f434f
--- /dev/null
+++ b/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/tests/threads/tests.c b/tests/threads/tests.c
new file mode 100644
index 0000000000000000000000000000000000000000..0d2b06b7b3f5ef0b187d6ddfdc8a4a06b7798370
--- /dev/null
+++ b/tests/threads/tests.c
@@ -0,0 +1,95 @@
+#include "tests/threads/tests.h"
+
+#include <debug.h>
+#include <stdio.h>
+#include <string.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},
+};
+
+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/tests/threads/tests.h b/tests/threads/tests.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b071fa6f431adf37f5adecb504b9e50784db005
--- /dev/null
+++ b/tests/threads/tests.h
@@ -0,0 +1,40 @@
+#ifndef TESTS_THREADS_TESTS_H
+#define TESTS_THREADS_TESTS_H
+
+void run_test(const char*);
+
+typedef void test_func(void);
+
+extern test_func test_alarm_single;
+extern test_func test_alarm_multiple;
+extern test_func test_alarm_simultaneous;
+// extern test_func test_alarm_priority;
+extern test_func test_alarm_zero;
+extern test_func test_alarm_negative;
+// extern test_func test_priority_change;
+// extern test_func test_priority_donate_one;
+// extern test_func test_priority_donate_multiple;
+// extern test_func test_priority_donate_multiple2;
+// extern test_func test_priority_donate_sema;
+// extern test_func test_priority_donate_nest;
+// extern test_func test_priority_donate_lower;
+// extern test_func test_priority_donate_chain;
+// extern test_func test_priority_fifo;
+// extern test_func test_priority_preempt;
+// extern test_func test_priority_sema;
+// extern test_func test_priority_condvar;
+// extern test_func test_mlfqs_load_1;
+// extern test_func test_mlfqs_load_60;
+// extern test_func test_mlfqs_load_avg;
+// extern test_func test_mlfqs_recent_1;
+// extern test_func test_mlfqs_fair_2;
+// extern test_func test_mlfqs_fair_20;
+// extern test_func test_mlfqs_nice_2;
+// extern test_func test_mlfqs_nice_10;
+// extern test_func test_mlfqs_block;
+
+void msg(const char*, ...);
+void fail(const char*, ...);
+void pass(void);
+
+#endif /* tests/threads/tests.h */
diff --git a/tests/userprog/Grading b/tests/userprog/Grading
new file mode 100644
index 0000000000000000000000000000000000000000..f70dc99acef673fc5a6408c0f7498dc7b027468f
--- /dev/null
+++ b/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/tests/userprog/Make.tests b/tests/userprog/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..f22961b3436a7299e0d86cecc984785e76e34202
--- /dev/null
+++ b/tests/userprog/Make.tests
@@ -0,0 +1,157 @@
+# -*- makefile -*-
+
+tests/%.output: FILESYSSOURCE = --filesys-size=2
+tests/%.output: PUTFILES = $(filter-out kernel.bin loader.bin, $^)
+
+# tests/userprog_TESTS = $(addprefix tests/userprog/,args-none            \
+# args-single args-multiple args-many args-dbl-space sc-bad-sp            \
+# sc-bad-arg sc-boundary sc-boundary-2 sc-boundary-3 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-bound exec-bound-2                 \
+# exec-bound-3 exec-multiple exec-missing exec-bad-ptr wait-simple        \
+# wait-twice wait-killed wait-bad-pid multi-recurse multi-child-fd        \
+# rox-simple rox-child rox-multichild bad-read bad-write bad-read2        \
+# bad-write2 bad-jump bad-jump2)
+
+tests/userprog_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 sc-boundary-3 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-bound exec-bound-2                 \
+exec-multiple exec-missing exec-bad-ptr wait-simple        \
+wait-twice wait-killed wait-bad-pid multi-recurse multi-child-fd        \
+bad-read bad-write bad-read2 bad-write2 bad-jump bad-jump2)
+
+tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
+tests/userprog/,child-simple child-args child-bad child-close child-rox)
+
+tests/userprog/args-none_SRC = tests/userprog/args.c
+tests/userprog/args-single_SRC = tests/userprog/args.c
+tests/userprog/args-multiple_SRC = tests/userprog/args.c
+tests/userprog/args-many_SRC = tests/userprog/args.c
+tests/userprog/args-dbl-space_SRC = tests/userprog/args.c
+tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c
+tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c
+tests/userprog/bad-read_SRC = tests/userprog/bad-read.c tests/main.c
+tests/userprog/bad-write_SRC = tests/userprog/bad-write.c tests/main.c
+tests/userprog/bad-jump_SRC = tests/userprog/bad-jump.c tests/main.c
+tests/userprog/bad-read2_SRC = tests/userprog/bad-read2.c tests/main.c
+tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c
+tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c
+tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c           \
+tests/userprog/boundary.c tests/main.c
+tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c	\
+tests/userprog/boundary.c tests/main.c
+tests/userprog/sc-boundary-3_SRC = tests/userprog/sc-boundary-3.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-bound_SRC = tests/userprog/exec-bound.c       \
+tests/userprog/boundary.c  tests/main.c
+tests/userprog/exec-bound-2_SRC = tests/userprog/exec-bound-2.c         \
+tests/userprog/boundary.c  tests/main.c
+tests/userprog/exec-bound-3_SRC = tests/userprog/exec-bound-3.c         \
+tests/userprog/boundary.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/exec-bound_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/tests/userprog/Rubric.functionality b/tests/userprog/Rubric.functionality
new file mode 100644
index 0000000000000000000000000000000000000000..ea76c44d6561e43e3598acfde440150ffb600fd7
--- /dev/null
+++ b/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/tests/userprog/Rubric.robustness b/tests/userprog/Rubric.robustness
new file mode 100644
index 0000000000000000000000000000000000000000..b7d1035a745bb18b140e5a6aa6c9e32ba63acac5
--- /dev/null
+++ b/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/tests/userprog/args-dbl-space.ck b/tests/userprog/args-dbl-space.ck
new file mode 100644
index 0000000000000000000000000000000000000000..dfbcf4b878dc452455038e71a0406c8eac570a16
--- /dev/null
+++ b/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/tests/userprog/args-many.ck b/tests/userprog/args-many.ck
new file mode 100644
index 0000000000000000000000000000000000000000..214574a828a4c67a17f7d7b7996866325d08888c
--- /dev/null
+++ b/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/tests/userprog/args-multiple.ck b/tests/userprog/args-multiple.ck
new file mode 100644
index 0000000000000000000000000000000000000000..227e6cc97ffc8c0230cd748bd20fcc1a612e411a
--- /dev/null
+++ b/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/tests/userprog/args-none.ck b/tests/userprog/args-none.ck
new file mode 100644
index 0000000000000000000000000000000000000000..146318e0c330cf7e65cb1dede2462251b60c8d37
--- /dev/null
+++ b/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/tests/userprog/args-single.ck b/tests/userprog/args-single.ck
new file mode 100644
index 0000000000000000000000000000000000000000..24582b4da4b7e8d8ca00968cbe4986c4fe21a0ad
--- /dev/null
+++ b/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/tests/userprog/args.c b/tests/userprog/args.c
new file mode 100644
index 0000000000000000000000000000000000000000..949a9263e4dd0c878a244b04adf4a91dfdfdbef6
--- /dev/null
+++ b/tests/userprog/args.c
@@ -0,0 +1,24 @@
+/* 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/tests/userprog/bad-jump.c b/tests/userprog/bad-jump.c
new file mode 100644
index 0000000000000000000000000000000000000000..6593003bd5f207b02c6c313f45c4bae36128e9a6
--- /dev/null
+++ b/tests/userprog/bad-jump.c
@@ -0,0 +1,14 @@
+/* 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"
+
+typedef int (*volatile functionptr)(void);
+
+void test_main(void)
+{
+	functionptr fp = NULL;
+	msg("Congratulations - you have successfully called NULL: %d", fp());
+	fail("should have exited with -1");
+}
diff --git a/tests/userprog/bad-jump.ck b/tests/userprog/bad-jump.ck
new file mode 100644
index 0000000000000000000000000000000000000000..e1c178b4427f6aa7bae76e9bd41ef74c78248d36
--- /dev/null
+++ b/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/tests/userprog/bad-jump2.c b/tests/userprog/bad-jump2.c
new file mode 100644
index 0000000000000000000000000000000000000000..ceb82921d402d3313871d720f9badf82afe0e485
--- /dev/null
+++ b/tests/userprog/bad-jump2.c
@@ -0,0 +1,12 @@
+/* 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/tests/userprog/bad-jump2.ck b/tests/userprog/bad-jump2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..35f0f9790ba4f64bc1200411a8f660568f8d559f
--- /dev/null
+++ b/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/tests/userprog/bad-read.c b/tests/userprog/bad-read.c
new file mode 100644
index 0000000000000000000000000000000000000000..4fa82df13f4707f8baf9662bfcd506c2a4b92aaf
--- /dev/null
+++ b/tests/userprog/bad-read.c
@@ -0,0 +1,12 @@
+/* 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",
+		 *(volatile int*) NULL);
+	fail("should have exited with -1");
+}
diff --git a/tests/userprog/bad-read.ck b/tests/userprog/bad-read.ck
new file mode 100644
index 0000000000000000000000000000000000000000..4d4d9266646fe2b275a5ded0aac32e8695fa3a56
--- /dev/null
+++ b/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/tests/userprog/bad-read2.c b/tests/userprog/bad-read2.c
new file mode 100644
index 0000000000000000000000000000000000000000..85afdbc4da4e38c76962923bad95bab8de4eba6e
--- /dev/null
+++ b/tests/userprog/bad-read2.c
@@ -0,0 +1,12 @@
+/* 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/tests/userprog/bad-read2.ck b/tests/userprog/bad-read2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..fa27c7dc70ffd80dab51e1258be5ecd6eee86c9a
--- /dev/null
+++ b/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/tests/userprog/bad-write.c b/tests/userprog/bad-write.c
new file mode 100644
index 0000000000000000000000000000000000000000..68f9f0760f27cf09801cde3f6e51ba59f94e87fc
--- /dev/null
+++ b/tests/userprog/bad-write.c
@@ -0,0 +1,11 @@
+/* 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)
+{
+	*(volatile int*) NULL = 42;
+	fail("should have exited with -1");
+}
diff --git a/tests/userprog/bad-write.ck b/tests/userprog/bad-write.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d213b49a91afcd4995de560404e41be5557e9a63
--- /dev/null
+++ b/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/tests/userprog/bad-write2.c b/tests/userprog/bad-write2.c
new file mode 100644
index 0000000000000000000000000000000000000000..1cf6f4d3e2a24779d90da8c775db2b9bdc0ea92b
--- /dev/null
+++ b/tests/userprog/bad-write2.c
@@ -0,0 +1,11 @@
+/* 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/tests/userprog/bad-write2.ck b/tests/userprog/bad-write2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c6a34200ca6658368300c054b8faae57699c304f
--- /dev/null
+++ b/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/tests/userprog/boundary.c b/tests/userprog/boundary.c
new file mode 100644
index 0000000000000000000000000000000000000000..84a42929aefbd0f62b377c1a1daf98919c68b618
--- /dev/null
+++ b/tests/userprog/boundary.c
@@ -0,0 +1,45 @@
+/* Utility function for tests that try to break system calls by
+	passing them data that crosses from one virtual page to
+	another. */
+
+#include "tests/userprog/boundary.h"
+
+#include <inttypes.h>
+#include <round.h>
+#include <string.h>
+
+/* Together with statements in src/lib/user/user.lds, arranges
+	for the following array to be at the very end of the .bss
+	segment (needed for get_bad_boundary below). */
+static char dst[8192] __attribute__((section(".testEndmem,\"aw\",@nobits#")));
+
+/* 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;
+}
+
+/* Returns an address that is invalid, but the preceding bytes
+ * are all valid (the highest address in the bss segment). Used
+ * to position information such that the first byte of the
+ * information is valid, but not all the information is valid. */
+void* get_bad_boundary(void)
+{
+	/* This code assumes that dst will be in the highest page
+	 * allocated to the user process. */
+	return (void*) ROUND_UP((uintptr_t) (dst + sizeof(dst) - 1), 4096);
+}
diff --git a/tests/userprog/boundary.h b/tests/userprog/boundary.h
new file mode 100644
index 0000000000000000000000000000000000000000..18642d33ac04f2ed7ee22ef01841a72c1b5726bb
--- /dev/null
+++ b/tests/userprog/boundary.h
@@ -0,0 +1,8 @@
+#ifndef TESTS_USERPROG_BOUNDARY_H
+#define TESTS_USERPROG_BOUNDARY_H
+
+void* get_boundary_area(void);
+char* copy_string_across_boundary(const char*);
+void* get_bad_boundary(void);
+
+#endif /* tests/userprog/boundary.h */
diff --git a/tests/userprog/child-bad.c b/tests/userprog/child-bad.c
new file mode 100644
index 0000000000000000000000000000000000000000..d49e0d6c709a7d4749ec90fae0402a3f1e7432cc
--- /dev/null
+++ b/tests/userprog/child-bad.c
@@ -0,0 +1,13 @@
+/* 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/tests/userprog/child-close.c b/tests/userprog/child-close.c
new file mode 100644
index 0000000000000000000000000000000000000000..cb8f5aa26028b488391c9344ee8e2011848ef7e0
--- /dev/null
+++ b/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 "tests/lib.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+int main(int argc UNUSED, char* argv[])
+{
+	test_name = "child-close";
+
+	msg("begin");
+	if (!isdigit(*argv[1]))
+		fail("bad command-line arguments");
+	close(atoi(argv[1]));
+	msg("end");
+
+	return 0;
+}
diff --git a/tests/userprog/child-rox.c b/tests/userprog/child-rox.c
new file mode 100644
index 0000000000000000000000000000000000000000..1f8cd7941f3c816646a0caeaf12bfbb0d75fddf1
--- /dev/null
+++ b/tests/userprog/child-rox.c
@@ -0,0 +1,52 @@
+/* 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 "tests/lib.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+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[])
+{
+	test_name = "child-rox";
+
+	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/tests/userprog/child-simple.c b/tests/userprog/child-simple.c
new file mode 100644
index 0000000000000000000000000000000000000000..c11a3604ba14c21dfd3c07d7a92b0612e8a3cc44
--- /dev/null
+++ b/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 "tests/lib.h"
+
+#include <stdio.h>
+
+int main(void)
+{
+	test_name = "child-simple";
+
+	msg("run");
+	return 81;
+}
diff --git a/tests/userprog/close-bad-fd.c b/tests/userprog/close-bad-fd.c
new file mode 100644
index 0000000000000000000000000000000000000000..21983df33b81279e526d1846252b09523f0af981
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	close(0x20101234);
+}
diff --git a/tests/userprog/close-bad-fd.ck b/tests/userprog/close-bad-fd.ck
new file mode 100644
index 0000000000000000000000000000000000000000..497b17c1156806128b59c8859b37168133210f2f
--- /dev/null
+++ b/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/tests/userprog/close-normal.c b/tests/userprog/close-normal.c
new file mode 100644
index 0000000000000000000000000000000000000000..add06b655c9458f30616a0eed2dc0967e7e1afe7
--- /dev/null
+++ b/tests/userprog/close-normal.c
@@ -0,0 +1,14 @@
+/* Opens a file and then closes it. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/close-normal.ck b/tests/userprog/close-normal.ck
new file mode 100644
index 0000000000000000000000000000000000000000..fe4134287aa673826e87cb40807e2ad62d81bd0c
--- /dev/null
+++ b/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/tests/userprog/close-stdin.c b/tests/userprog/close-stdin.c
new file mode 100644
index 0000000000000000000000000000000000000000..c4ed49a8f5494f7dbd485e07350ae7822345f573
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	close(0);
+}
diff --git a/tests/userprog/close-stdin.ck b/tests/userprog/close-stdin.ck
new file mode 100644
index 0000000000000000000000000000000000000000..3d285074f505fd82fe9a3028bc2f4e9c1883f267
--- /dev/null
+++ b/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/tests/userprog/close-stdout.c b/tests/userprog/close-stdout.c
new file mode 100644
index 0000000000000000000000000000000000000000..2d2b33bd1fdec0726c81c712a165f2bc06062ff3
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	close(1);
+}
diff --git a/tests/userprog/close-stdout.ck b/tests/userprog/close-stdout.ck
new file mode 100644
index 0000000000000000000000000000000000000000..3cbbcff5c5407785be4ce34a89f0ff29709914c9
--- /dev/null
+++ b/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/tests/userprog/close-twice.c b/tests/userprog/close-twice.c
new file mode 100644
index 0000000000000000000000000000000000000000..d1d4399bae0609620b79eddee40948e1d42b26e5
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/close-twice.ck b/tests/userprog/close-twice.ck
new file mode 100644
index 0000000000000000000000000000000000000000..deb55a6d3c613d6917808e9b4ec9d02020189fd1
--- /dev/null
+++ b/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/tests/userprog/create-bad-ptr.c b/tests/userprog/create-bad-ptr.c
new file mode 100644
index 0000000000000000000000000000000000000000..007c96a54a5d44b2317aad8c0a6671b033128482
--- /dev/null
+++ b/tests/userprog/create-bad-ptr.c
@@ -0,0 +1,11 @@
+/* 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/tests/userprog/create-bad-ptr.ck b/tests/userprog/create-bad-ptr.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ac134056634333f8ddd7d11274a17f7c91caf29b
--- /dev/null
+++ b/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/tests/userprog/create-bound.c b/tests/userprog/create-bound.c
new file mode 100644
index 0000000000000000000000000000000000000000..2e8a2ab6369365dd2deb5115008ad18f7defecfe
--- /dev/null
+++ b/tests/userprog/create-bound.c
@@ -0,0 +1,13 @@
+/* Opens a file whose name spans the boundary between two pages.
+	This is valid, so it must succeed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("create(\"quux.dat\"): %d", create(copy_string_across_boundary("quux.dat"), 0));
+}
diff --git a/tests/userprog/create-bound.ck b/tests/userprog/create-bound.ck
new file mode 100644
index 0000000000000000000000000000000000000000..7656b7fe8e50d57b2637e02f5797683ae5660144
--- /dev/null
+++ b/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/tests/userprog/create-empty.c b/tests/userprog/create-empty.c
new file mode 100644
index 0000000000000000000000000000000000000000..5e83859579be886ac895129e1a0c725598b9830f
--- /dev/null
+++ b/tests/userprog/create-empty.c
@@ -0,0 +1,9 @@
+/* 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/tests/userprog/create-empty.ck b/tests/userprog/create-empty.ck
new file mode 100644
index 0000000000000000000000000000000000000000..93a10588453b20446e9a26eb8fc838e22791cf67
--- /dev/null
+++ b/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/tests/userprog/create-exists.c b/tests/userprog/create-exists.c
new file mode 100644
index 0000000000000000000000000000000000000000..13a7e4e4f0a0c2425067a0031c10f37d4ac29d0d
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/create-exists.ck b/tests/userprog/create-exists.ck
new file mode 100644
index 0000000000000000000000000000000000000000..006885ef82e49efcc10a7147fece62dec86a882a
--- /dev/null
+++ b/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/tests/userprog/create-long.c b/tests/userprog/create-long.c
new file mode 100644
index 0000000000000000000000000000000000000000..e0b2bf7a8f056f79f62ae9259dc4c6c529e08e62
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.h>
+#include <syscall.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/tests/userprog/create-long.ck b/tests/userprog/create-long.ck
new file mode 100644
index 0000000000000000000000000000000000000000..628411cd53cd081460613fbfbbaa095e73366b1b
--- /dev/null
+++ b/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/tests/userprog/create-normal.c b/tests/userprog/create-normal.c
new file mode 100644
index 0000000000000000000000000000000000000000..17169298c26c649c50d992bca2a58bf296948a14
--- /dev/null
+++ b/tests/userprog/create-normal.c
@@ -0,0 +1,9 @@
+/* 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/tests/userprog/create-normal.ck b/tests/userprog/create-normal.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ca74a6eac7a40514bafaaeb3048c9458ed24aa98
--- /dev/null
+++ b/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/tests/userprog/create-null.c b/tests/userprog/create-null.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1c7e4867b557d7a7e1a935154435e86297940bb
--- /dev/null
+++ b/tests/userprog/create-null.c
@@ -0,0 +1,10 @@
+/* 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/tests/userprog/create-null.ck b/tests/userprog/create-null.ck
new file mode 100644
index 0000000000000000000000000000000000000000..09b7872aa67cf9cecfe9908eff7ed7a77cd53a77
--- /dev/null
+++ b/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/tests/userprog/exec-arg.c b/tests/userprog/exec-arg.c
new file mode 100644
index 0000000000000000000000000000000000000000..3ed3d89e35424b44c53d283e850fe7819df24c68
--- /dev/null
+++ b/tests/userprog/exec-arg.c
@@ -0,0 +1,10 @@
+/* Tests argument passing to child processes. */
+
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	wait(exec("child-args childarg"));
+}
diff --git a/tests/userprog/exec-arg.ck b/tests/userprog/exec-arg.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b7533ed9d81499d33bf090c61dc75426ffc82266
--- /dev/null
+++ b/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/tests/userprog/exec-bad-ptr.c b/tests/userprog/exec-bad-ptr.c
new file mode 100644
index 0000000000000000000000000000000000000000..d42fd0f04d81e2e8b02d037ceb45eedcefb3ee91
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	exec((char*) 0x20101234);
+}
diff --git a/tests/userprog/exec-bad-ptr.ck b/tests/userprog/exec-bad-ptr.ck
new file mode 100644
index 0000000000000000000000000000000000000000..63f5f78cc2d6be86d3fac76aa1d8f7fd6628f0d1
--- /dev/null
+++ b/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/tests/userprog/exec-bound-2.c b/tests/userprog/exec-bound-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..26540dd94ff42f9c7ca4fe35fc6886ce2671a733
--- /dev/null
+++ b/tests/userprog/exec-bound-2.c
@@ -0,0 +1,20 @@
+/* Invokes an exec system call with the exec string pointer argument
+	positioned such that only its first byte is valid  memory (bytes 1-3
+	of the pointer are invalid).  Must kill process. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall-nr.h>
+
+void test_main(void)
+{
+	char* p = get_bad_boundary() - 5;
+	*((int*) p) = SYS_EXEC;
+	p[4] = '!';
+
+	/* Invoke the system call. */
+	asm volatile("movl %0, %%esp; int $0x30" : : "g"(p));
+	fail("should have killed process");
+}
diff --git a/tests/userprog/exec-bound-2.ck b/tests/userprog/exec-bound-2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..0be2946fdbb3c51427955c3da4b570094fdf66dc
--- /dev/null
+++ b/tests/userprog/exec-bound-2.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-bound-2) begin
+exec-bound-2: exit(-1)
+EOF
+pass;
diff --git a/tests/userprog/exec-bound-3.c b/tests/userprog/exec-bound-3.c
new file mode 100644
index 0000000000000000000000000000000000000000..033291531ef8ad7df2351593b432b6de95219d1c
--- /dev/null
+++ b/tests/userprog/exec-bound-3.c
@@ -0,0 +1,28 @@
+/* Invokes an exec system call with the exec string straddling a
+	page boundary such that the first byte of the string is valid
+	but the remainder of the string is in invalid memory. Must
+	kill process. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall-nr.h>
+
+void test_main(void)
+{
+	char* p = get_bad_boundary() - 1;
+	*p = 'a';
+	exec(p);
+
+	/* Note: if this test fails to pass even with the official solutions,
+		it's probably because memory layout has changed and p no longer
+		refers to the proper page boundary. To fix the problem, uncomment
+		the line below to print out the boundary address. In addition,
+		add a printf line in load_segment to print out the address range
+		of each segment. From that, you'll be able to figure out how to
+		modify get_bad_boundary to make things work again. */
+
+	// msg("boundary address: 0x%x", p);
+	fail("should have killed process");
+}
diff --git a/tests/userprog/exec-bound-3.ck b/tests/userprog/exec-bound-3.ck
new file mode 100644
index 0000000000000000000000000000000000000000..93fed651c71127bef707003b2bf9062f774c822f
--- /dev/null
+++ b/tests/userprog/exec-bound-3.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-bound-3) begin
+exec-bound-3: exit(-1)
+EOF
+pass;
diff --git a/tests/userprog/exec-bound.c b/tests/userprog/exec-bound.c
new file mode 100644
index 0000000000000000000000000000000000000000..535564b163a629dfd43d12e7658dafc146053aef
--- /dev/null
+++ b/tests/userprog/exec-bound.c
@@ -0,0 +1,12 @@
+/* Exec a child with an exec string that spans a page boundary. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	wait(exec(copy_string_across_boundary("child-args arg1 arg2")));
+}
diff --git a/tests/userprog/exec-bound.ck b/tests/userprog/exec-bound.ck
new file mode 100644
index 0000000000000000000000000000000000000000..62e588f42961233ae986b4e25f05720dc4133ee8
--- /dev/null
+++ b/tests/userprog/exec-bound.ck
@@ -0,0 +1,18 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(exec-bound) begin
+(args) begin
+(args) argc = 3
+(args) argv[0] = 'child-args'
+(args) argv[1] = 'arg1'
+(args) argv[2] = 'arg2'
+(args) argv[3] = null
+(args) end
+child-args: exit(0)
+(exec-bound) end
+exec-bound: exit(0)
+EOF
+pass;
diff --git a/tests/userprog/exec-missing.c b/tests/userprog/exec-missing.c
new file mode 100644
index 0000000000000000000000000000000000000000..b69e21558d4e86fe81e05e798441de3e393d910e
--- /dev/null
+++ b/tests/userprog/exec-missing.c
@@ -0,0 +1,12 @@
+/* Tries to execute a nonexistent process.
+	The exec system call must return -1. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("exec(\"no-such-file\"): %d", exec("no-such-file"));
+}
diff --git a/tests/userprog/exec-missing.ck b/tests/userprog/exec-missing.ck
new file mode 100644
index 0000000000000000000000000000000000000000..0ef7aaa76d4728a67ac571058ad33daad3466ac2
--- /dev/null
+++ b/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/tests/userprog/exec-multiple.c b/tests/userprog/exec-multiple.c
new file mode 100644
index 0000000000000000000000000000000000000000..290e98b6d69b80f9b44caee8f4fc6e014915f7c1
--- /dev/null
+++ b/tests/userprog/exec-multiple.c
@@ -0,0 +1,14 @@
+/* Executes and waits for multiple child processes. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	wait(exec("child-simple"));
+	wait(exec("child-simple"));
+	wait(exec("child-simple"));
+	wait(exec("child-simple"));
+}
diff --git a/tests/userprog/exec-multiple.ck b/tests/userprog/exec-multiple.ck
new file mode 100644
index 0000000000000000000000000000000000000000..99624cd7bd87174462fb6dc1635485e1b7b2557d
--- /dev/null
+++ b/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/tests/userprog/exec-once.c b/tests/userprog/exec-once.c
new file mode 100644
index 0000000000000000000000000000000000000000..b420e21242dc001405fa15449f1f2ea00bb3787c
--- /dev/null
+++ b/tests/userprog/exec-once.c
@@ -0,0 +1,11 @@
+/* Executes and waits for a single child process. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	wait(exec("child-simple"));
+}
diff --git a/tests/userprog/exec-once.ck b/tests/userprog/exec-once.ck
new file mode 100644
index 0000000000000000000000000000000000000000..00b59ededcc1fde171a5b58ed013ae5fd19ac4b4
--- /dev/null
+++ b/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/tests/userprog/exit.c b/tests/userprog/exit.c
new file mode 100644
index 0000000000000000000000000000000000000000..a1a803887c77ea8124d8c97636a41399c0c0340a
--- /dev/null
+++ b/tests/userprog/exit.c
@@ -0,0 +1,10 @@
+/* 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/tests/userprog/exit.ck b/tests/userprog/exit.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a552702b61a9a880471746dad64b17787b9b2941
--- /dev/null
+++ b/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/tests/userprog/halt.c b/tests/userprog/halt.c
new file mode 100644
index 0000000000000000000000000000000000000000..011064ecc6554e3623fd3e007003367c41a4363d
--- /dev/null
+++ b/tests/userprog/halt.c
@@ -0,0 +1,10 @@
+/* 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/tests/userprog/halt.ck b/tests/userprog/halt.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1b701ed9076f8192e3e4e0247b2817ea10341dfb
--- /dev/null
+++ b/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/tests/userprog/lib/.gitignore b/tests/userprog/lib/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a4383358ec72fb4d15f30791cb5265a6e06c5416
--- /dev/null
+++ b/tests/userprog/lib/.gitignore
@@ -0,0 +1 @@
+*.d
diff --git a/tests/userprog/lib/user/.dummy b/tests/userprog/lib/user/.dummy
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/userprog/lib/user/.gitignore b/tests/userprog/lib/user/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a4383358ec72fb4d15f30791cb5265a6e06c5416
--- /dev/null
+++ b/tests/userprog/lib/user/.gitignore
@@ -0,0 +1 @@
+*.d
diff --git a/tests/userprog/multi-child-fd.c b/tests/userprog/multi-child-fd.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a656bb59186cee594498431acae147143188e6c
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/sample.inc"
+
+#include <stdio.h>
+#include <syscall.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/tests/userprog/multi-child-fd.ck b/tests/userprog/multi-child-fd.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d0b3a33922258bb1c310294db3eba63d2bcedf9a
--- /dev/null
+++ b/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/tests/userprog/multi-recurse.c b/tests/userprog/multi-recurse.c
new file mode 100644
index 0000000000000000000000000000000000000000..3ff23ed88709668fc7e2ffee28ca6a8d2e26fe07
--- /dev/null
+++ b/tests/userprog/multi-recurse.c
@@ -0,0 +1,33 @@
+/* Executes itself recursively to the depth indicated by the
+	first command-line argument. */
+
+#include "tests/lib.h"
+
+#include <debug.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syscall.h>
+
+int main(int argc UNUSED, char* argv[])
+{
+	int n = atoi(argv[1]);
+
+	test_name = "multi-recurse";
+
+	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/tests/userprog/multi-recurse.ck b/tests/userprog/multi-recurse.ck
new file mode 100644
index 0000000000000000000000000000000000000000..41eb4a6e5a325e7b3da7a93b232399b9cca75ec3
--- /dev/null
+++ b/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/tests/userprog/no-vm/Make.tests b/tests/userprog/no-vm/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..a545e18eff1f159522e2ad9fb719279e176a85ca
--- /dev/null
+++ b/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/tests/userprog/no-vm/Rubric b/tests/userprog/no-vm/Rubric
new file mode 100644
index 0000000000000000000000000000000000000000..c3816c660fa087f64d8af500a3c5cb6f71a5b72d
--- /dev/null
+++ b/tests/userprog/no-vm/Rubric
@@ -0,0 +1,3 @@
+Functionality of features that VM might break:
+
+1	multi-oom
diff --git a/tests/userprog/no-vm/multi-oom.c b/tests/userprog/no-vm/multi-oom.c
new file mode 100644
index 0000000000000000000000000000000000000000..41e4735e6fab9fe58f2449336ec925a106ac00ae
--- /dev/null
+++ b/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 "tests/lib.h"
+
+#include <debug.h>
+#include <random.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+
+static const int EXPECTED_DEPTH_TO_PASS = 30;
+static const int EXPECTED_REPETITIONS = 10;
+
+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);
+	volatile int* PHYS_BASE = (volatile int*) 0xC0000000;
+
+	switch (random_ulong() % 5) {
+		case 0:
+			*(volatile int*) NULL = 42;
+
+		case 1:
+			return *(volatile 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;
+
+	test_name = "multi-oom";
+
+	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/tests/userprog/no-vm/multi-oom.ck b/tests/userprog/no-vm/multi-oom.ck
new file mode 100644
index 0000000000000000000000000000000000000000..59a0bcd5324c899fc093a7fd24505d615d4ba778
--- /dev/null
+++ b/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/tests/userprog/null.ck b/tests/userprog/null.ck
new file mode 100644
index 0000000000000000000000000000000000000000..980de35ca6cb582cd8f0cad26465569dd021e74c
--- /dev/null
+++ b/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/tests/userprog/open-bad-ptr.c b/tests/userprog/open-bad-ptr.c
new file mode 100644
index 0000000000000000000000000000000000000000..586476afb5042783a2b892c32ee9716e693f0eef
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("open(0x20101234): %d", open((char*) 0x20101234));
+	fail("should have called exit(-1)");
+}
diff --git a/tests/userprog/open-bad-ptr.ck b/tests/userprog/open-bad-ptr.ck
new file mode 100644
index 0000000000000000000000000000000000000000..45349e2c91537b8034c1089a6a721c3360867410
--- /dev/null
+++ b/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/tests/userprog/open-boundary.c b/tests/userprog/open-boundary.c
new file mode 100644
index 0000000000000000000000000000000000000000..a3d2ee66653830fefc3635979fe629473b8bf098
--- /dev/null
+++ b/tests/userprog/open-boundary.c
@@ -0,0 +1,13 @@
+/* Creates a file whose name spans the boundary between two pages.
+	This is valid, so it must succeed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	CHECK(open(copy_string_across_boundary("sample.txt")) > 1, "open \"sample.txt\"");
+}
diff --git a/tests/userprog/open-boundary.ck b/tests/userprog/open-boundary.ck
new file mode 100644
index 0000000000000000000000000000000000000000..8060d223c2436831785a8591f8bb3dbc0a8540f1
--- /dev/null
+++ b/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/tests/userprog/open-empty.c b/tests/userprog/open-empty.c
new file mode 100644
index 0000000000000000000000000000000000000000..2214da03d8eebc68e2107fab637ba8046aa34f08
--- /dev/null
+++ b/tests/userprog/open-empty.c
@@ -0,0 +1,13 @@
+/* Tries to open a file with the empty string as its name. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	int handle = open("");
+	if (handle != -1)
+		fail("open() returned %d instead of -1", handle);
+}
diff --git a/tests/userprog/open-empty.ck b/tests/userprog/open-empty.ck
new file mode 100644
index 0000000000000000000000000000000000000000..885fb415cd84fdb2adb69c60fa65c2b50124b11b
--- /dev/null
+++ b/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/tests/userprog/open-missing.c b/tests/userprog/open-missing.c
new file mode 100644
index 0000000000000000000000000000000000000000..34a01fc8932b491f72baeebcf5c20ca00980b72b
--- /dev/null
+++ b/tests/userprog/open-missing.c
@@ -0,0 +1,13 @@
+/* Tries to open a nonexistent file. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	int handle = open("no-such-file");
+	if (handle != -1)
+		fail("open() returned %d", handle);
+}
diff --git a/tests/userprog/open-missing.ck b/tests/userprog/open-missing.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d72d87868c8e31edce579f852ee9de9999cbff4c
--- /dev/null
+++ b/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/tests/userprog/open-normal.c b/tests/userprog/open-normal.c
new file mode 100644
index 0000000000000000000000000000000000000000..0e68f121aefec74da65f2a4e0c41a522de2c2be7
--- /dev/null
+++ b/tests/userprog/open-normal.c
@@ -0,0 +1,13 @@
+/* Open a file. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	int handle = open("sample.txt");
+	if (handle < 2)
+		fail("open() returned %d", handle);
+}
diff --git a/tests/userprog/open-normal.ck b/tests/userprog/open-normal.ck
new file mode 100644
index 0000000000000000000000000000000000000000..4f6c34250f94d8bfa8ad92c83efba8b631fba460
--- /dev/null
+++ b/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/tests/userprog/open-null.c b/tests/userprog/open-null.c
new file mode 100644
index 0000000000000000000000000000000000000000..8db36dfdb4c71e870795a626b7fdba6b40dc2231
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <stddef.h>
+#include <syscall.h>
+
+void test_main(void)
+{
+	open(NULL);
+}
diff --git a/tests/userprog/open-null.ck b/tests/userprog/open-null.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b4a3bcb4853b0908a52e2db38bde5e41938fbf24
--- /dev/null
+++ b/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/tests/userprog/open-twice.c b/tests/userprog/open-twice.c
new file mode 100644
index 0000000000000000000000000000000000000000..bcd8bea553140a77e0761b610e78b86f9b3ab1fd
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/open-twice.ck b/tests/userprog/open-twice.ck
new file mode 100644
index 0000000000000000000000000000000000000000..64fa805b277434a2b7a48c5d6ffa73dad64f812d
--- /dev/null
+++ b/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/tests/userprog/read-bad-fd.c b/tests/userprog/read-bad-fd.c
new file mode 100644
index 0000000000000000000000000000000000000000..5d193d654f08455ae05937fb5e4a6397dc288fa1
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <limits.h>
+#include <syscall.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/tests/userprog/read-bad-fd.ck b/tests/userprog/read-bad-fd.ck
new file mode 100644
index 0000000000000000000000000000000000000000..5fedcc7c7f6600ec87df48f65851c01322c31f50
--- /dev/null
+++ b/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/tests/userprog/read-bad-ptr.c b/tests/userprog/read-bad-ptr.c
new file mode 100644
index 0000000000000000000000000000000000000000..baa857850e83cf221bebfa971ec2534b455bcbac
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/read-bad-ptr.ck b/tests/userprog/read-bad-ptr.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d10accfd599805b4e3768c0dc4d9df7ead3df416
--- /dev/null
+++ b/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/tests/userprog/read-boundary.c b/tests/userprog/read-boundary.c
new file mode 100644
index 0000000000000000000000000000000000000000..3265b0e016896b2110b37d96becbcecaa60c3977
--- /dev/null
+++ b/tests/userprog/read-boundary.c
@@ -0,0 +1,29 @@
+/* Reads data spanning two pages in virtual address space,
+	which must succeed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+#include "tests/userprog/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/userprog/read-boundary.ck b/tests/userprog/read-boundary.ck
new file mode 100644
index 0000000000000000000000000000000000000000..08dc1612734966f0ad460ffb5a589e9e6aa8583c
--- /dev/null
+++ b/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/tests/userprog/read-normal.c b/tests/userprog/read-normal.c
new file mode 100644
index 0000000000000000000000000000000000000000..de3e3da10b716d57ff1a2d5b6acd296a01d108e6
--- /dev/null
+++ b/tests/userprog/read-normal.c
@@ -0,0 +1,10 @@
+/* Try reading a file in the most normal way. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/sample.inc"
+
+void test_main(void)
+{
+	check_file("sample.txt", sample, sizeof sample - 1);
+}
diff --git a/tests/userprog/read-normal.ck b/tests/userprog/read-normal.ck
new file mode 100644
index 0000000000000000000000000000000000000000..0ed299828315945e98eb39e3b4ca3525d4837e6b
--- /dev/null
+++ b/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/tests/userprog/read-stdout.c b/tests/userprog/read-stdout.c
new file mode 100644
index 0000000000000000000000000000000000000000..829b573cc609ffb198603078378ff55280b328cf
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <stdio.h>
+#include <syscall.h>
+
+void test_main(void)
+{
+	char buf;
+	read(STDOUT_FILENO, &buf, 1);
+}
diff --git a/tests/userprog/read-stdout.ck b/tests/userprog/read-stdout.ck
new file mode 100644
index 0000000000000000000000000000000000000000..7d87b52b316aaad180da2035679a3a466eacc3b4
--- /dev/null
+++ b/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/tests/userprog/read-zero.c b/tests/userprog/read-zero.c
new file mode 100644
index 0000000000000000000000000000000000000000..305c2305d569091e06106f4913bb0b9f613fcab4
--- /dev/null
+++ b/tests/userprog/read-zero.c
@@ -0,0 +1,22 @@
+/* Try a 0-byte read, which should return 0 without reading
+	anything. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/read-zero.ck b/tests/userprog/read-zero.ck
new file mode 100644
index 0000000000000000000000000000000000000000..8346dbc325a2d74887ab6837c294e024643bd928
--- /dev/null
+++ b/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/tests/userprog/rox-child.c b/tests/userprog/rox-child.c
new file mode 100644
index 0000000000000000000000000000000000000000..a61e2a35866e7243f430b149818dcc8f7084be2f
--- /dev/null
+++ b/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/tests/userprog/rox-child.ck b/tests/userprog/rox-child.ck
new file mode 100644
index 0000000000000000000000000000000000000000..e6363fb6104437513ed564e13a0842ae2afabf1b
--- /dev/null
+++ b/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/tests/userprog/rox-child.inc b/tests/userprog/rox-child.inc
new file mode 100644
index 0000000000000000000000000000000000000000..1e2ade9c5fd89e3c159adc18a863113002215308
--- /dev/null
+++ b/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/tests/userprog/rox-multichild.c b/tests/userprog/rox-multichild.c
new file mode 100644
index 0000000000000000000000000000000000000000..30254650e4deecc5fa2966b7728dce46a95d7789
--- /dev/null
+++ b/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/tests/userprog/rox-multichild.ck b/tests/userprog/rox-multichild.ck
new file mode 100644
index 0000000000000000000000000000000000000000..14b27db7e1d2246ffd606c77db987e8e1de61532
--- /dev/null
+++ b/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/tests/userprog/rox-simple.c b/tests/userprog/rox-simple.c
new file mode 100644
index 0000000000000000000000000000000000000000..d4d8728d7990b91b886476c7e0e2eb7f3d6a1bb8
--- /dev/null
+++ b/tests/userprog/rox-simple.c
@@ -0,0 +1,19 @@
+/* Ensure that the executable of a running process cannot be
+	modified. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/rox-simple.ck b/tests/userprog/rox-simple.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c9dcc663743cb3b1635e6e9c6b6813ed55ae08bd
--- /dev/null
+++ b/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/tests/userprog/sample.inc b/tests/userprog/sample.inc
new file mode 100644
index 0000000000000000000000000000000000000000..59f2bcb6c493673fe6b4cfa90067e60dfbf6bb36
--- /dev/null
+++ b/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/tests/userprog/sample.txt b/tests/userprog/sample.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5050fec989ccb23041ed7010d2973f834fd35123
--- /dev/null
+++ b/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/tests/userprog/sc-bad-arg.c b/tests/userprog/sc-bad-arg.c
new file mode 100644
index 0000000000000000000000000000000000000000..a20f5fb8597e7241f0e6d5a9e8ad32d3059e3b34
--- /dev/null
+++ b/tests/userprog/sc-bad-arg.c
@@ -0,0 +1,18 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall-nr.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/tests/userprog/sc-bad-arg.ck b/tests/userprog/sc-bad-arg.ck
new file mode 100644
index 0000000000000000000000000000000000000000..89811056338ef199208fbf48d5a3471f1cf64266
--- /dev/null
+++ b/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/tests/userprog/sc-bad-sp.c b/tests/userprog/sc-bad-sp.c
new file mode 100644
index 0000000000000000000000000000000000000000..a4dc24284e561313e54f6930a95ddf143af3a57f
--- /dev/null
+++ b/tests/userprog/sc-bad-sp.c
@@ -0,0 +1,19 @@
+/* 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/tests/userprog/sc-bad-sp.ck b/tests/userprog/sc-bad-sp.ck
new file mode 100644
index 0000000000000000000000000000000000000000..498cec141f15846a071dd4fdf8b42eca3d91d32b
--- /dev/null
+++ b/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/tests/userprog/sc-boundary-2.c b/tests/userprog/sc-boundary-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..4ffd496920720a573a0fbc5a87665cbd05c41fad
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall-nr.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/tests/userprog/sc-boundary-2.ck b/tests/userprog/sc-boundary-2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..43766bf1b5ebfc0c67e4d8a0a45616e4568d81c5
--- /dev/null
+++ b/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/tests/userprog/sc-boundary-3.c b/tests/userprog/sc-boundary-3.c
new file mode 100644
index 0000000000000000000000000000000000000000..60fd402086c7422c688dd729cec206e653113025
--- /dev/null
+++ b/tests/userprog/sc-boundary-3.c
@@ -0,0 +1,20 @@
+/* Invokes a system call with the system call number positioned
+	such that its first byte is valid but the remaining bytes of
+	the number are in invalid memory. Must kill process. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall-nr.h>
+
+void test_main(void)
+{
+	char* p = get_bad_boundary();
+	p--;
+	*p = 100;
+
+	/* Invoke the system call. */
+	asm volatile("movl %0, %%esp; int $0x30" : : "g"(p));
+	fail("should have killed process");
+}
diff --git a/tests/userprog/sc-boundary-3.ck b/tests/userprog/sc-boundary-3.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a7ac8c34c5f8d0b464f0ba6e62dd473c5ac53fde
--- /dev/null
+++ b/tests/userprog/sc-boundary-3.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(sc-boundary-3) begin
+sc-boundary-3: exit(-1)
+EOF
+pass;
diff --git a/tests/userprog/sc-boundary.c b/tests/userprog/sc-boundary.c
new file mode 100644
index 0000000000000000000000000000000000000000..2f76f2473a61b637981c38da612624126b2ce722
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+
+#include <syscall-nr.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/tests/userprog/sc-boundary.ck b/tests/userprog/sc-boundary.ck
new file mode 100644
index 0000000000000000000000000000000000000000..3f7cbaf1ed41ad0df47d616e9d929562d3e8042d
--- /dev/null
+++ b/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/tests/userprog/wait-bad-pid.c b/tests/userprog/wait-bad-pid.c
new file mode 100644
index 0000000000000000000000000000000000000000..f8e7b8190e2c0b8c07268048c549742ed355c60d
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	wait((pid_t) 0x0c020301);
+}
diff --git a/tests/userprog/wait-bad-pid.ck b/tests/userprog/wait-bad-pid.ck
new file mode 100644
index 0000000000000000000000000000000000000000..db63fb990c0f921e9ae3ff8ab4accbefbfc0991c
--- /dev/null
+++ b/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/tests/userprog/wait-killed.c b/tests/userprog/wait-killed.c
new file mode 100644
index 0000000000000000000000000000000000000000..55741abb58e2a83568b9e8f1e7647bc11af36d5d
--- /dev/null
+++ b/tests/userprog/wait-killed.c
@@ -0,0 +1,11 @@
+/* Wait for a process that will be killed for bad behavior. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("wait(exec()) = %d", wait(exec("child-bad")));
+}
diff --git a/tests/userprog/wait-killed.ck b/tests/userprog/wait-killed.ck
new file mode 100644
index 0000000000000000000000000000000000000000..5df0e9c1330f5376e41241fe63a98692820d9bd2
--- /dev/null
+++ b/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/tests/userprog/wait-simple.c b/tests/userprog/wait-simple.c
new file mode 100644
index 0000000000000000000000000000000000000000..b7cffdb6b4bfa59cedf7b44f1293098e26034217
--- /dev/null
+++ b/tests/userprog/wait-simple.c
@@ -0,0 +1,11 @@
+/* Wait for a subprocess to finish. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	msg("wait(exec()) = %d", wait(exec("child-simple")));
+}
diff --git a/tests/userprog/wait-simple.ck b/tests/userprog/wait-simple.ck
new file mode 100644
index 0000000000000000000000000000000000000000..93dd577667e053f61d10e6d48d0b8437da863256
--- /dev/null
+++ b/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/tests/userprog/wait-twice.c b/tests/userprog/wait-twice.c
new file mode 100644
index 0000000000000000000000000000000000000000..655af2f20955d7bc6c4a83fa3caff343ffdc6f7e
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/wait-twice.ck b/tests/userprog/wait-twice.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6d5384319a2c91078a397aa83fd8d1f0d75dcc9c
--- /dev/null
+++ b/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/tests/userprog/write-bad-fd.c b/tests/userprog/write-bad-fd.c
new file mode 100644
index 0000000000000000000000000000000000000000..e5a385d8eecfd04a46e4cf04fb0ed991567aaa56
--- /dev/null
+++ b/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 "tests/main.h"
+
+#include <limits.h>
+#include <syscall.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/tests/userprog/write-bad-fd.ck b/tests/userprog/write-bad-fd.ck
new file mode 100644
index 0000000000000000000000000000000000000000..8da7a8bbe79a5c1f0520ebc0d4c62318b70e73fa
--- /dev/null
+++ b/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/tests/userprog/write-bad-ptr.c b/tests/userprog/write-bad-ptr.c
new file mode 100644
index 0000000000000000000000000000000000000000..8263a5e247006517b4565797872ae947ecf431f1
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/write-bad-ptr.ck b/tests/userprog/write-bad-ptr.ck
new file mode 100644
index 0000000000000000000000000000000000000000..ad9f399c81bb1c4d87306d2266ec555d96cfec45
--- /dev/null
+++ b/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/tests/userprog/write-boundary.c b/tests/userprog/write-boundary.c
new file mode 100644
index 0000000000000000000000000000000000000000..8eb215f14ac0fb7226faf6984782fdadcb35efcc
--- /dev/null
+++ b/tests/userprog/write-boundary.c
@@ -0,0 +1,25 @@
+/* Writes data spanning two pages in virtual address space,
+	which must succeed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/boundary.h"
+#include "tests/userprog/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/userprog/write-boundary.ck b/tests/userprog/write-boundary.ck
new file mode 100644
index 0000000000000000000000000000000000000000..788378179c57064823ab362c2176edafdc1b3c3f
--- /dev/null
+++ b/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/tests/userprog/write-normal.c b/tests/userprog/write-normal.c
new file mode 100644
index 0000000000000000000000000000000000000000..50f7959fd2e66e1422a87dd5731acd55cdd31507
--- /dev/null
+++ b/tests/userprog/write-normal.c
@@ -0,0 +1,19 @@
+/* Try writing a file in the most normal way. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/userprog/sample.inc"
+
+#include <syscall.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/tests/userprog/write-normal.ck b/tests/userprog/write-normal.ck
new file mode 100644
index 0000000000000000000000000000000000000000..9fa6024c09a679df06bcef847f2132fc07e39c35
--- /dev/null
+++ b/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/tests/userprog/write-stdin.c b/tests/userprog/write-stdin.c
new file mode 100644
index 0000000000000000000000000000000000000000..20c3cb392cdc53e2e06143e5102c8ac633cff41b
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	char buf = 123;
+	write(0, &buf, 1);
+}
diff --git a/tests/userprog/write-stdin.ck b/tests/userprog/write-stdin.ck
new file mode 100644
index 0000000000000000000000000000000000000000..a6caf81edd6a213c5bd97c907247d2954865d50d
--- /dev/null
+++ b/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/tests/userprog/write-zero.c b/tests/userprog/write-zero.c
new file mode 100644
index 0000000000000000000000000000000000000000..cf66342bf45104461e3c0d873478c5f39626265c
--- /dev/null
+++ b/tests/userprog/write-zero.c
@@ -0,0 +1,20 @@
+/* Try a 0-byte write, which should return 0 without writing
+	anything. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/userprog/write-zero.ck b/tests/userprog/write-zero.ck
new file mode 100644
index 0000000000000000000000000000000000000000..cc4cd609f4e402ad5fa77e19c438a0edde02bcd6
--- /dev/null
+++ b/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/tests/vm/Grading b/tests/vm/Grading
new file mode 100644
index 0000000000000000000000000000000000000000..f0c2c13251154fc10c08090c0f2120b2c84847c3
--- /dev/null
+++ b/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/tests/vm/Make.tests b/tests/vm/Make.tests
new file mode 100644
index 0000000000000000000000000000000000000000..04b1b81666ac0407ffd05a7157048eabc6e6f911
--- /dev/null
+++ b/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/tests/vm/Rubric.functionality b/tests/vm/Rubric.functionality
new file mode 100644
index 0000000000000000000000000000000000000000..8a8661290c6e31a1937418ea0d5b33b1038b18ab
--- /dev/null
+++ b/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/tests/vm/Rubric.robustness b/tests/vm/Rubric.robustness
new file mode 100644
index 0000000000000000000000000000000000000000..0b2552f29852e4e1121d99ddd8ed7537aa414abc
--- /dev/null
+++ b/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/tests/vm/child-inherit.c b/tests/vm/child-inherit.c
new file mode 100644
index 0000000000000000000000000000000000000000..db77ac64fdf639ed07a43f2c25064b2acaf732eb
--- /dev/null
+++ b/tests/vm/child-inherit.c
@@ -0,0 +1,15 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+
+void test_main(void)
+{
+	memset((char*) 0x54321000, 0, 4096);
+	fail("child can modify parent's memory mappings");
+}
diff --git a/tests/vm/child-linear.c b/tests/vm/child-linear.c
new file mode 100644
index 0000000000000000000000000000000000000000..b4980b8411d9f218d71286e7664b8d37b5652669
--- /dev/null
+++ b/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 "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.h>
+
+#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;
+
+	test_name = "child-linear";
+
+	/* 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/tests/vm/child-mm-wrt.c b/tests/vm/child-mm-wrt.c
new file mode 100644
index 0000000000000000000000000000000000000000..7cb4fe4c81811d88e1991802a5227e6ea7c70017
--- /dev/null
+++ b/tests/vm/child-mm-wrt.c
@@ -0,0 +1,23 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/child-qsort-mm.c b/tests/vm/child-qsort-mm.c
new file mode 100644
index 0000000000000000000000000000000000000000..4fe1459a75b93a9966f88ab3f50b3f02e8c74eb8
--- /dev/null
+++ b/tests/vm/child-qsort-mm.c
@@ -0,0 +1,24 @@
+/* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort,
+	a multi-pass divide and conquer algorithm.  */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+#include <debug.h>
+#include <syscall.h>
+
+int main(int argc UNUSED, char* argv[])
+{
+	int handle;
+	unsigned char* p = (unsigned char*) 0x10000000;
+
+	test_name = "child-qsort-mm";
+	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/tests/vm/child-qsort.c b/tests/vm/child-qsort.c
new file mode 100644
index 0000000000000000000000000000000000000000..a3c8b4ce452982c99554b751a587d55d66b5a8a4
--- /dev/null
+++ b/tests/vm/child-qsort.c
@@ -0,0 +1,31 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+#include <debug.h>
+#include <syscall.h>
+
+int main(int argc UNUSED, char* argv[])
+{
+	int handle;
+	unsigned char buf[128 * 1024];
+	size_t size;
+
+	test_name = "child-qsort";
+	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/tests/vm/child-sort.c b/tests/vm/child-sort.c
new file mode 100644
index 0000000000000000000000000000000000000000..77c900873b08f2bc75a78f87ebd1d65437a62474
--- /dev/null
+++ b/tests/vm/child-sort.c
@@ -0,0 +1,38 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <debug.h>
+#include <syscall.h>
+
+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;
+
+	test_name = "child-sort";
+	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/tests/vm/mmap-bad-fd.c b/tests/vm/mmap-bad-fd.c
new file mode 100644
index 0000000000000000000000000000000000000000..b328b95d98f897b4760994ecec11111d98de3743
--- /dev/null
+++ b/tests/vm/mmap-bad-fd.c
@@ -0,0 +1,13 @@
+/* Tries to mmap an invalid fd,
+	which must either fail silently or terminate the process with
+	exit code -1. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.h>
+
+void test_main(void)
+{
+	CHECK(mmap(0x5678, (void*) 0x10000000) == MAP_FAILED, "try to mmap invalid fd");
+}
diff --git a/tests/vm/mmap-bad-fd.ck b/tests/vm/mmap-bad-fd.ck
new file mode 100644
index 0000000000000000000000000000000000000000..f3f58d577994d2a05bda9396eae9df81d4cd110f
--- /dev/null
+++ b/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/tests/vm/mmap-clean.c b/tests/vm/mmap-clean.c
new file mode 100644
index 0000000000000000000000000000000000000000..df068c537f03e1c471eff546a872a231c206f596
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-clean.ck b/tests/vm/mmap-clean.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1666d6cd469b93d1190681cae13bd8b51f24a221
--- /dev/null
+++ b/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/tests/vm/mmap-close.c b/tests/vm/mmap-close.c
new file mode 100644
index 0000000000000000000000000000000000000000..67403d4a22138526a1e42e378e1535c37fa94c4f
--- /dev/null
+++ b/tests/vm/mmap-close.c
@@ -0,0 +1,27 @@
+/* Verifies that memory mappings persist after file close. */
+
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-close.ck b/tests/vm/mmap-close.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d15e41ae7ba255217ed947ccf95f87d4693456ff
--- /dev/null
+++ b/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/tests/vm/mmap-exit.c b/tests/vm/mmap-exit.c
new file mode 100644
index 0000000000000000000000000000000000000000..c231d8bd4e421e987f67f7f72ebe9155817d10a5
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <syscall.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/tests/vm/mmap-exit.ck b/tests/vm/mmap-exit.ck
new file mode 100644
index 0000000000000000000000000000000000000000..457d34a029b8854a31690d9b12000ad09b2f71e4
--- /dev/null
+++ b/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/tests/vm/mmap-inherit.c b/tests/vm/mmap-inherit.c
new file mode 100644
index 0000000000000000000000000000000000000000..381c8d5d87168b2775acd9d13c9e936fdfa512ca
--- /dev/null
+++ b/tests/vm/mmap-inherit.c
@@ -0,0 +1,33 @@
+/* Maps a file into memory and runs child-inherit to verify that
+	mappings are not inherited. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-inherit.ck b/tests/vm/mmap-inherit.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c54638a0f928645dd904032b55079d0d123510b2
--- /dev/null
+++ b/tests/vm/mmap-inherit.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(mmap-inherit) begin
+(mmap-inherit) open "sample.txt"
+(mmap-inherit) mmap "sample.txt"
+(mmap-inherit) exec "child-inherit"
+(child-inherit) begin
+child-inherit: exit(-1)
+(mmap-inherit) checking that mmap'd file still has same data
+(mmap-inherit) end
+mmap-inherit: exit(0)
+EOF
+pass;
diff --git a/tests/vm/mmap-misalign.c b/tests/vm/mmap-misalign.c
new file mode 100644
index 0000000000000000000000000000000000000000..bba0c7ff1c0ecb471e49222042eb3de432bfb127
--- /dev/null
+++ b/tests/vm/mmap-misalign.c
@@ -0,0 +1,16 @@
+/* Verifies that misaligned memory mappings are disallowed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/vm/mmap-misalign.ck b/tests/vm/mmap-misalign.ck
new file mode 100644
index 0000000000000000000000000000000000000000..145a2e895db69ce129677e816156d620af54189c
--- /dev/null
+++ b/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/tests/vm/mmap-null.c b/tests/vm/mmap-null.c
new file mode 100644
index 0000000000000000000000000000000000000000..55b2c4143b4bef54912a13b23a27510f570a4909
--- /dev/null
+++ b/tests/vm/mmap-null.c
@@ -0,0 +1,14 @@
+/* Verifies that memory mappings at address 0 are disallowed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/vm/mmap-null.ck b/tests/vm/mmap-null.ck
new file mode 100644
index 0000000000000000000000000000000000000000..aacdd65f524be22822f80dd485e9879984303faf
--- /dev/null
+++ b/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/tests/vm/mmap-over-code.c b/tests/vm/mmap-over-code.c
new file mode 100644
index 0000000000000000000000000000000000000000..fe64db89540da592e1e7a14071c759356da766ce
--- /dev/null
+++ b/tests/vm/mmap-over-code.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the code segment is disallowed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <round.h>
+#include <stdint.h>
+#include <syscall.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/tests/vm/mmap-over-code.ck b/tests/vm/mmap-over-code.ck
new file mode 100644
index 0000000000000000000000000000000000000000..b5b23c7b9930f458cff3ae209e64bd41d46c7499
--- /dev/null
+++ b/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/tests/vm/mmap-over-data.c b/tests/vm/mmap-over-data.c
new file mode 100644
index 0000000000000000000000000000000000000000..9189341dbd4f439a7e7edbe91e8d60413500d37a
--- /dev/null
+++ b/tests/vm/mmap-over-data.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the data segment is disallowed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <round.h>
+#include <stdint.h>
+#include <syscall.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/tests/vm/mmap-over-data.ck b/tests/vm/mmap-over-data.ck
new file mode 100644
index 0000000000000000000000000000000000000000..98770cccf6e66faa49d2e4c73bc7ad5ac152a8b5
--- /dev/null
+++ b/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/tests/vm/mmap-over-stk.c b/tests/vm/mmap-over-stk.c
new file mode 100644
index 0000000000000000000000000000000000000000..5ff1397f2dac60627649aedf80d970b3e10e1d63
--- /dev/null
+++ b/tests/vm/mmap-over-stk.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the stack segment is disallowed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <round.h>
+#include <stdint.h>
+#include <syscall.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/tests/vm/mmap-over-stk.ck b/tests/vm/mmap-over-stk.ck
new file mode 100644
index 0000000000000000000000000000000000000000..e6880cfbe58497e8020edfc595598176efd6e0a7
--- /dev/null
+++ b/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/tests/vm/mmap-overlap.c b/tests/vm/mmap-overlap.c
new file mode 100644
index 0000000000000000000000000000000000000000..1e53b62b0b9fae8dce6828fc2b856c6703917ba9
--- /dev/null
+++ b/tests/vm/mmap-overlap.c
@@ -0,0 +1,18 @@
+/* Verifies that overlapping memory mappings are disallowed. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <syscall.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/tests/vm/mmap-overlap.ck b/tests/vm/mmap-overlap.ck
new file mode 100644
index 0000000000000000000000000000000000000000..f13801e7a806226ce90d88bb3bd8926591c9ef57
--- /dev/null
+++ b/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/tests/vm/mmap-read.c b/tests/vm/mmap-read.c
new file mode 100644
index 0000000000000000000000000000000000000000..9d8378ddc4897a8e16eba01c081ee1bb89fc7582
--- /dev/null
+++ b/tests/vm/mmap-read.c
@@ -0,0 +1,31 @@
+/* Uses a memory mapping to read a file. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-read.ck b/tests/vm/mmap-read.ck
new file mode 100644
index 0000000000000000000000000000000000000000..95ab7901abfe297f10b9c5eb6f67a6525bad05f9
--- /dev/null
+++ b/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/tests/vm/mmap-remove.c b/tests/vm/mmap-remove.c
new file mode 100644
index 0000000000000000000000000000000000000000..0279ad843d95826a98ae611c81e55da8032dc36f
--- /dev/null
+++ b/tests/vm/mmap-remove.c
@@ -0,0 +1,42 @@
+/* Deletes and closes file that is mapped into memory
+	and verifies that it can still be read through the mapping. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-remove.ck b/tests/vm/mmap-remove.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d3cc93847d0e0a64b0214d4ac47c5968361600fe
--- /dev/null
+++ b/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/tests/vm/mmap-shuffle.c b/tests/vm/mmap-shuffle.c
new file mode 100644
index 0000000000000000000000000000000000000000..c2ba361e58de123afec95c21cfb507373bbd9e23
--- /dev/null
+++ b/tests/vm/mmap-shuffle.c
@@ -0,0 +1,36 @@
+/* Creates a 128 kB file and repeatedly shuffles data in it
+	through a memory mapping. */
+
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-shuffle.ck b/tests/vm/mmap-shuffle.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c15830170311a2dc78429351b2f1ef963b30a74e
--- /dev/null
+++ b/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/tests/vm/mmap-twice.c b/tests/vm/mmap-twice.c
new file mode 100644
index 0000000000000000000000000000000000000000..09e4af93144239679ec3500780e9e152dc3055e8
--- /dev/null
+++ b/tests/vm/mmap-twice.c
@@ -0,0 +1,31 @@
+/* Maps the same file into memory twice and verifies that the
+	same data is readable in both. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-twice.ck b/tests/vm/mmap-twice.ck
new file mode 100644
index 0000000000000000000000000000000000000000..05e972459e7e83c9eea89a9c81153200a303873f
--- /dev/null
+++ b/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/tests/vm/mmap-unmap.c b/tests/vm/mmap-unmap.c
new file mode 100644
index 0000000000000000000000000000000000000000..3e16d5274cabb668a047e0dee1cf8e3f3c95aaef
--- /dev/null
+++ b/tests/vm/mmap-unmap.c
@@ -0,0 +1,23 @@
+/* Maps and unmaps a file and verifies that the mapped region is
+	inaccessible afterward. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <syscall.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/tests/vm/mmap-unmap.ck b/tests/vm/mmap-unmap.ck
new file mode 100644
index 0000000000000000000000000000000000000000..119658c68108451aa1964298cd8e8f513b275bc8
--- /dev/null
+++ b/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/tests/vm/mmap-write.c b/tests/vm/mmap-write.c
new file mode 100644
index 0000000000000000000000000000000000000000..f6ab21f4a39b2069812a05d45b0939c9fb17528e
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/mmap-write.ck b/tests/vm/mmap-write.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d2c9cc511f43da3eb8f0910477925757310efd6d
--- /dev/null
+++ b/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/tests/vm/mmap-zero.c b/tests/vm/mmap-zero.c
new file mode 100644
index 0000000000000000000000000000000000000000..c01ce551fd75e1dfb9d05f2bfffffcb5907095b1
--- /dev/null
+++ b/tests/vm/mmap-zero.c
@@ -0,0 +1,26 @@
+/* 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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/vm/mmap-zero.ck b/tests/vm/mmap-zero.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6033d5d34f0404784bb3d379229fdf14eeed5516
--- /dev/null
+++ b/tests/vm/mmap-zero.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(mmap-zero) begin
+(mmap-zero) create empty file "empty"
+(mmap-zero) open "empty"
+(mmap-zero) mmap "empty"
+mmap-zero: exit(-1)
+EOF
+pass;
diff --git a/tests/vm/page-linear.c b/tests/vm/page-linear.c
new file mode 100644
index 0000000000000000000000000000000000000000..c26a7533aabba06aafb6310e324a7d1040057dab
--- /dev/null
+++ b/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 "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.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/tests/vm/page-linear.ck b/tests/vm/page-linear.ck
new file mode 100644
index 0000000000000000000000000000000000000000..dcbc8843efa2571e63f99a7c9530b004010d2e4c
--- /dev/null
+++ b/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/tests/vm/page-merge-mm.c b/tests/vm/page-merge-mm.c
new file mode 100644
index 0000000000000000000000000000000000000000..b4eb9047d3832fed13923a601aae23232747e791
--- /dev/null
+++ b/tests/vm/page-merge-mm.c
@@ -0,0 +1,7 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void test_main(void)
+{
+	parallel_merge("child-qsort-mm", 80);
+}
diff --git a/tests/vm/page-merge-mm.ck b/tests/vm/page-merge-mm.ck
new file mode 100644
index 0000000000000000000000000000000000000000..74fa980fb73d8e102a2cd61503a1ddcd6b7aa471
--- /dev/null
+++ b/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/tests/vm/page-merge-par.c b/tests/vm/page-merge-par.c
new file mode 100644
index 0000000000000000000000000000000000000000..803f9d403b6bc42f664b0941775e910827c6309c
--- /dev/null
+++ b/tests/vm/page-merge-par.c
@@ -0,0 +1,7 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void test_main(void)
+{
+	parallel_merge("child-sort", 123);
+}
diff --git a/tests/vm/page-merge-par.ck b/tests/vm/page-merge-par.ck
new file mode 100644
index 0000000000000000000000000000000000000000..31f8aa7dc3f7cb3b02fa6ed40336f6de25c074fb
--- /dev/null
+++ b/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/tests/vm/page-merge-seq.c b/tests/vm/page-merge-seq.c
new file mode 100644
index 0000000000000000000000000000000000000000..526ce32b00c6fe85a8384333c2838fbf59664f1e
--- /dev/null
+++ b/tests/vm/page-merge-seq.c
@@ -0,0 +1,125 @@
+/* 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 "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/vm/page-merge-seq.ck b/tests/vm/page-merge-seq.ck
new file mode 100644
index 0000000000000000000000000000000000000000..d78f69da534a19813ba9d7bb3ab28b6095f3563f
--- /dev/null
+++ b/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/tests/vm/page-merge-stk.c b/tests/vm/page-merge-stk.c
new file mode 100644
index 0000000000000000000000000000000000000000..9aa15e943c1587eaa2ffe35bbc9a0defa3693b0b
--- /dev/null
+++ b/tests/vm/page-merge-stk.c
@@ -0,0 +1,7 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void test_main(void)
+{
+	parallel_merge("child-qsort", 72);
+}
diff --git a/tests/vm/page-merge-stk.ck b/tests/vm/page-merge-stk.ck
new file mode 100644
index 0000000000000000000000000000000000000000..c5bc1ae05db6efc3e1f7a87cab995bb91af367ff
--- /dev/null
+++ b/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/tests/vm/page-parallel.c b/tests/vm/page-parallel.c
new file mode 100644
index 0000000000000000000000000000000000000000..c685934b2f9d13f0a4df09ffc3b9ba0d4b385e4b
--- /dev/null
+++ b/tests/vm/page-parallel.c
@@ -0,0 +1,20 @@
+/* Runs 4 child-linear processes at once. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/vm/page-parallel.ck b/tests/vm/page-parallel.ck
new file mode 100644
index 0000000000000000000000000000000000000000..90c14ef035e6e1a0db6d017c970bf9b179045ab8
--- /dev/null
+++ b/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/tests/vm/page-shuffle.c b/tests/vm/page-shuffle.c
new file mode 100644
index 0000000000000000000000000000000000000000..e1f414f75eb0089a3cd5b41a50a506919a8badbb
--- /dev/null
+++ b/tests/vm/page-shuffle.c
@@ -0,0 +1,28 @@
+/* Shuffles a 128 kB data buffer 10 times, printing the checksum
+	after each time. */
+
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdbool.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/tests/vm/page-shuffle.ck b/tests/vm/page-shuffle.ck
new file mode 100644
index 0000000000000000000000000000000000000000..6447d380eba51719547768c6702835e383d80340
--- /dev/null
+++ b/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/tests/vm/parallel-merge.c b/tests/vm/parallel-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..6106b3e9f7b2eb63cc24f8c6cc2a367b3f316ade
--- /dev/null
+++ b/tests/vm/parallel-merge.c
@@ -0,0 +1,138 @@
+/* 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 "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <stdio.h>
+#include <syscall.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/tests/vm/parallel-merge.h b/tests/vm/parallel-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..633b972f9948911ad6090ae41a696467477aa8fa
--- /dev/null
+++ b/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/tests/vm/process_death.pm b/tests/vm/process_death.pm
new file mode 100644
index 0000000000000000000000000000000000000000..52039a1a1986939c52e074d2faf8d200bef508ac
--- /dev/null
+++ b/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/tests/vm/pt-bad-addr.c b/tests/vm/pt-bad-addr.c
new file mode 100644
index 0000000000000000000000000000000000000000..b844f840145072543424030694bf28123fb30387
--- /dev/null
+++ b/tests/vm/pt-bad-addr.c
@@ -0,0 +1,10 @@
+/* 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/tests/vm/pt-bad-addr.ck b/tests/vm/pt-bad-addr.ck
new file mode 100644
index 0000000000000000000000000000000000000000..09ea039d45e2772075c7c195c5341d6506c1df8d
--- /dev/null
+++ b/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/tests/vm/pt-bad-read.c b/tests/vm/pt-bad-read.c
new file mode 100644
index 0000000000000000000000000000000000000000..0c6dc326b981852d73a82cec740b17a8b7a24457
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+
+#include <syscall.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/tests/vm/pt-bad-read.ck b/tests/vm/pt-bad-read.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1f96bb445d8869b5976b7a75f248476ee308ea20
--- /dev/null
+++ b/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/tests/vm/pt-big-stk-obj.c b/tests/vm/pt-big-stk-obj.c
new file mode 100644
index 0000000000000000000000000000000000000000..87488188b778c00d91d21058c82de2df24fcf7f4
--- /dev/null
+++ b/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 "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.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/tests/vm/pt-big-stk-obj.ck b/tests/vm/pt-big-stk-obj.ck
new file mode 100644
index 0000000000000000000000000000000000000000..eb5853a23576ba65428d10251331c5672ac0dd2e
--- /dev/null
+++ b/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/tests/vm/pt-grow-bad.c b/tests/vm/pt-grow-bad.c
new file mode 100644
index 0000000000000000000000000000000000000000..491e6b77af3649ad49c0a7991735a849feff60aa
--- /dev/null
+++ b/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 "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.h>
+
+void test_main(void)
+{
+	asm volatile("movl -4096(%esp), %eax");
+}
diff --git a/tests/vm/pt-grow-bad.ck b/tests/vm/pt-grow-bad.ck
new file mode 100644
index 0000000000000000000000000000000000000000..4c0ab8a2bc7c146bb6322204803a92af82153ec2
--- /dev/null
+++ b/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/tests/vm/pt-grow-pusha.c b/tests/vm/pt-grow-pusha.c
new file mode 100644
index 0000000000000000000000000000000000000000..43fe61d54f984816a161697b413d39ea76c4666b
--- /dev/null
+++ b/tests/vm/pt-grow-pusha.c
@@ -0,0 +1,22 @@
+/* Expand the stack by 32 bytes all at once using the PUSHA
+	instruction.
+	This must succeed. */
+
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.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/tests/vm/pt-grow-pusha.ck b/tests/vm/pt-grow-pusha.ck
new file mode 100644
index 0000000000000000000000000000000000000000..5000966ed473eaaad125bc8879f4e47202b71414
--- /dev/null
+++ b/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/tests/vm/pt-grow-stack.c b/tests/vm/pt-grow-stack.c
new file mode 100644
index 0000000000000000000000000000000000000000..8c594579f709d36003dff37b7bfc9153cd8a923c
--- /dev/null
+++ b/tests/vm/pt-grow-stack.c
@@ -0,0 +1,20 @@
+/* Demonstrate that the stack can grow.
+	This must succeed. */
+
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#include <string.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/tests/vm/pt-grow-stack.ck b/tests/vm/pt-grow-stack.ck
new file mode 100644
index 0000000000000000000000000000000000000000..1e669db94c360f64295857ad83f6d04e24122b2c
--- /dev/null
+++ b/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/tests/vm/pt-grow-stk-sc.c b/tests/vm/pt-grow-stk-sc.c
new file mode 100644
index 0000000000000000000000000000000000000000..90716a03c231d1bae6517d93b316198ccce708fd
--- /dev/null
+++ b/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 "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/sample.inc"
+
+#include <string.h>
+#include <syscall.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/tests/vm/pt-grow-stk-sc.ck b/tests/vm/pt-grow-stk-sc.ck
new file mode 100644
index 0000000000000000000000000000000000000000..23d3b02e8af0d6d6cc6b7a8fc149b43a1c7c14a1
--- /dev/null
+++ b/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/tests/vm/pt-write-code-2.c b/tests/vm/pt-write-code-2.c
new file mode 100644
index 0000000000000000000000000000000000000000..373bee51caa0be5933a3aa60736d568c73ba7129
--- /dev/null
+++ b/tests/vm/pt-write-code-2.c
@@ -0,0 +1,14 @@
+/* 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/tests/vm/pt-write-code.c b/tests/vm/pt-write-code.c
new file mode 100644
index 0000000000000000000000000000000000000000..197eda506500b97303f2bd1cbb444b222c929cd8
--- /dev/null
+++ b/tests/vm/pt-write-code.c
@@ -0,0 +1,11 @@
+/* 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/tests/vm/pt-write-code.ck b/tests/vm/pt-write-code.ck
new file mode 100644
index 0000000000000000000000000000000000000000..65610fb505b7330bc92b4284878462b076ff7f9c
--- /dev/null
+++ b/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/tests/vm/pt-write-code2.ck b/tests/vm/pt-write-code2.ck
new file mode 100644
index 0000000000000000000000000000000000000000..69ffc77ca4b3a7960adcdb91b66da10b0bb4fd96
--- /dev/null
+++ b/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/tests/vm/qsort.c b/tests/vm/qsort.c
new file mode 100644
index 0000000000000000000000000000000000000000..73d59520451ccc1673a2b451a97d486989356a1f
--- /dev/null
+++ b/tests/vm/qsort.c
@@ -0,0 +1,123 @@
+#include "tests/vm/qsort.h"
+
+#include <debug.h>
+#include <random.h>
+#include <stdbool.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/tests/vm/qsort.h b/tests/vm/qsort.h
new file mode 100644
index 0000000000000000000000000000000000000000..de2b3cd0d5f5f75904410338e15f4932f0655647
--- /dev/null
+++ b/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/tests/vm/sample.inc b/tests/vm/sample.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a60a139a94e1bdb1dea9a76c1a70005b9c67869d
--- /dev/null
+++ b/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/tests/vm/sample.txt b/tests/vm/sample.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c4468305e5790f5fe0c525b363161a4dacb5a786
--- /dev/null
+++ b/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/threads/.gitignore b/threads/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6d5357c015ab6f0b7ee7074381afdd3da82e06eb
--- /dev/null
+++ b/threads/.gitignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/threads/Make.vars b/threads/Make.vars
new file mode 100644
index 0000000000000000000000000000000000000000..545cc251440ba3212558874836dea64ebf668d01
--- /dev/null
+++ b/threads/Make.vars
@@ -0,0 +1,7 @@
+# -*- makefile -*-
+
+kernel.bin: DEFINES =
+KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS)
+TEST_SUBDIRS = tests/threads
+GRADING_FILE = $(SRCDIR)/tests/threads/Grading
+SIMULATOR = --qemu
diff --git a/threads/Makefile b/threads/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..34c10aa4f508714da040e81389fa51e56ba2d97a
--- /dev/null
+++ b/threads/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel
diff --git a/threads/flags.h b/threads/flags.h
new file mode 100644
index 0000000000000000000000000000000000000000..fc69075abe44e11e0fe1d2b1bbec7e32caf5765b
--- /dev/null
+++ b/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/threads/init.c b/threads/init.c
new file mode 100644
index 0000000000000000000000000000000000000000..68345e87fc48f28805d11199b3e24d2f7dd747c2
--- /dev/null
+++ b/threads/init.c
@@ -0,0 +1,442 @@
+#include "threads/init.h"
+
+#include "devices/input.h"
+#include "devices/kbd.h"
+#include "devices/rtc.h"
+#include "devices/serial.h"
+#include "devices/shutdown.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"
+
+#include <console.h>
+#include <debug.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <random.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef USERPROG
+#include "userprog/exception.h"
+#include "userprog/gdt.h"
+#include "userprog/process.h"
+#include "userprog/slowdown.h"
+#include "userprog/syscall.h"
+#include "userprog/tss.h"
+#else
+#include "tests/threads/tests.h"
+#endif
+#ifdef FILESYS
+#include "devices/block.h"
+#include "devices/ide.h"
+#include "filesys/filesys.h"
+#include "filesys/fsutil.h"
+#endif
+
+/* Page directory with kernel mappings only. */
+uint32_t* init_page_dir;
+
+#ifdef FILESYS
+/* -f: Format the file system? */
+static bool format_filesys;
+
+/* -filesys, -scratch, -swap: Names of block devices to use,
+	overriding the defaults. */
+static const char* filesys_bdev_name;
+static const char* scratch_bdev_name;
+#ifdef VM
+static const char* swap_bdev_name;
+#endif
+#endif /* FILESYS */
+
+/* -F: Set timer frequency */
+static uint16_t init_timer_freq = 1000;
+
+/* -S: Execute kernel thread slowly */
+static bool slow_kernel_threads = false;
+
+/* -tcl: Set limit on threads that can be created */
+int thread_create_limit = 0; /* Infinite */
+/* -fl: Maximum number of pages to put into palloc's free pool */
+static size_t free_page_limit = SIZE_MAX;
+/* -ul: Maximum number of pages to put into palloc's user pool. */
+static size_t user_page_limit = SIZE_MAX;
+
+static void bss_init(void);
+static void paging_init(void);
+
+static char** read_command_line(void);
+static char** parse_options(char** argv);
+static void run_actions(char** argv);
+static void usage(void);
+
+#ifdef FILESYS
+static void locate_block_devices(void);
+static void locate_block_device(enum block_type, const char* name);
+#endif
+
+int main(void) NO_RETURN;
+
+/* Pintos main program. */
+int main(void)
+{
+	char** argv;
+
+	/* Clear BSS. */
+	bss_init();
+
+	/* Break command line into arguments and parse options. */
+	argv = read_command_line();
+	argv = parse_options(argv);
+
+	/* Initialize ourselves as a thread so we can use locks,
+		then enable console locking. */
+	thread_init();
+	console_init();
+
+	/* Greet user. */
+	printf(
+		 "Pintos booting with %'" PRIu32 " kB RAM...\n", init_ram_pages * PGSIZE / 1024);
+
+	/* Initialize memory system. */
+	palloc_init(user_page_limit, free_page_limit);
+	malloc_init();
+	paging_init();
+
+	/* Segmentation. */
+#ifdef USERPROG
+	tss_init();
+	gdt_init();
+#endif
+
+	/* Initialize interrupt handlers. */
+	intr_init();
+	timer_init(init_timer_freq);
+	kbd_init();
+	input_init();
+#ifdef USERPROG
+	exception_init();
+	syscall_init();
+	if (slow_kernel_threads) {
+		slowdown_init();
+	}
+#endif
+
+	/* Start thread scheduler and enable interrupts. */
+	thread_start();
+	serial_init_queue();
+	timer_calibrate();
+
+#ifdef FILESYS
+	/* Initialize file system. */
+	ide_init();
+	locate_block_devices();
+	filesys_init(format_filesys);
+#endif
+
+	printf("Boot complete.\n");
+
+	/* Run actions specified on kernel command line. */
+	run_actions(argv);
+
+	/* Finish up. */
+	shutdown();
+	thread_exit();
+}
+
+/* Clear the "BSS", a segment that should be initialized to
+	zeros.  It isn't actually stored on disk or zeroed by the
+	kernel loader, so we have to zero it ourselves.
+
+	The start and end of the BSS segment is recorded by the
+	linker as _start_bss and _end_bss.  See kernel.lds. */
+static void bss_init(void)
+{
+	extern char _start_bss, _end_bss;
+	memset(&_start_bss, 0, &_end_bss - &_start_bss);
+}
+
+/* Populates the base page directory and page table with the
+	kernel virtual mapping, and then sets up the CPU to use the
+	new page directory.  Points init_page_dir to the page
+	directory it creates. */
+static void paging_init(void)
+{
+	uint32_t *pd, *pt;
+	size_t page;
+	extern char _start, _end_kernel_text;
+
+	pd = init_page_dir = palloc_get_page(PAL_ASSERT | PAL_ZERO);
+	pt = NULL;
+	for (page = 0; page < init_ram_pages; page++) {
+		uintptr_t paddr = page * PGSIZE;
+		char* vaddr = ptov(paddr);
+		size_t pde_idx = pd_no(vaddr);
+		size_t pte_idx = pt_no(vaddr);
+		bool in_kernel_text = &_start <= vaddr && vaddr < &_end_kernel_text;
+
+		if (pd[pde_idx] == 0) {
+			pt = palloc_get_page(PAL_ASSERT | PAL_ZERO);
+			pd[pde_idx] = pde_create(pt);
+		}
+
+		pt[pte_idx] = pte_create_kernel(vaddr, !in_kernel_text);
+	}
+
+	/* Store the physical address of the page directory into CR3
+		aka PDBR (page directory base register).  This activates our
+		new page tables immediately.  See [IA32-v2a] "MOV--Move
+		to/from Control Registers" and [IA32-v3a] 3.7.5 "Base Address
+		of the Page Directory". */
+	asm volatile("movl %0, %%cr3" : : "r"(vtop(init_page_dir)));
+}
+
+/* Breaks the kernel command line into words and returns them as
+	an argv-like array. */
+static char** read_command_line(void)
+{
+	static char* argv[LOADER_ARGS_LEN / 2 + 1];
+	char *p, *end;
+	int argc;
+	int i;
+
+	argc = *(uint32_t*) ptov(LOADER_ARG_CNT);
+	p = ptov(LOADER_ARGS);
+	end = p + LOADER_ARGS_LEN;
+	for (i = 0; i < argc; i++) {
+		if (p >= end)
+			PANIC("command line arguments overflow");
+
+		argv[i] = p;
+		p += strnlen(p, end - p) + 1;
+	}
+	argv[argc] = NULL;
+
+	/* Print kernel command line. */
+	printf("Kernel command line:");
+	for (i = 0; i < argc; i++)
+		if (strchr(argv[i], ' ') == NULL)
+			printf(" %s", argv[i]);
+		else
+			printf(" '%s'", argv[i]);
+	printf("\n");
+
+	return argv;
+}
+
+/* Parses options in ARGV[]
+	and returns the first non-option argument. */
+static char** parse_options(char** argv)
+{
+	for (; *argv != NULL && **argv == '-'; argv++) {
+		char* save_ptr;
+		char* name = strtok_r(*argv, "=", &save_ptr);
+		char* value = strtok_r(NULL, "", &save_ptr);
+
+		if (!strcmp(name, "-h"))
+			usage();
+		else if (!strcmp(name, "-q"))
+			shutdown_configure(SHUTDOWN_POWER_OFF);
+		else if (!strcmp(name, "-r"))
+			shutdown_configure(SHUTDOWN_REBOOT);
+		else if (!strcmp(name, "-F"))	  // klaar@ida
+			init_timer_freq = atoi(value);
+		else if (!strcmp(name, "-S"))	  // filst@ida
+			slow_kernel_threads = true;
+
+#ifdef FILESYS
+		else if (!strcmp(name, "-f"))
+			format_filesys = true;
+		else if (!strcmp(name, "-filesys"))
+			filesys_bdev_name = value;
+		else if (!strcmp(name, "-scratch"))
+			scratch_bdev_name = value;
+#ifdef VM
+		else if (!strcmp(name, "-swap"))
+			swap_bdev_name = value;
+#endif
+#endif
+		else if (!strcmp(name, "-rs"))
+			random_init(atoi(value));
+		else if (!strcmp(name, "-mlfqs"))
+			thread_mlfqs = true;
+#ifdef USERPROG
+		else if (!strcmp(name, "-ul"))
+			user_page_limit = atoi(value);
+		else if (!strcmp(name, "-fl"))
+			free_page_limit = atoi(value);
+		else if (!strcmp(name, "-tcl"))
+			thread_create_limit = atoi(value);
+#endif
+		else
+			PANIC("unknown option `%s' (use -h for help)", name);
+	}
+
+	/* Initialize the random number generator based on the system
+		time.  This has no effect if an "-rs" option was specified.
+
+		When running under Bochs, this is not enough by itself to
+		get a good seed value, because the pintos script sets the
+		initial time to a predictable value, not to the local time,
+		for reproducibility.  To fix this, give the "-r" option to
+		the pintos script to request real-time execution. */
+	random_init(rtc_get_time());
+
+	return argv;
+}
+
+/* Runs the task specified in ARGV[1]. */
+static void run_task(char** argv)
+{
+	const char* task = argv[1];
+
+	printf("Executing '%s':\n", task);
+#ifdef USERPROG
+	process_wait(process_execute(task));
+#else
+	run_test(task);
+#endif
+	printf("Execution of '%s' complete.\n", task);
+}
+
+/* Executes all of the actions specified in ARGV[]
+	up to the null pointer sentinel. */
+static void run_actions(char** argv)
+{
+	/* An action. */
+	struct action {
+		char* name;							 /* Action name. */
+		int argc;							 /* # of args, including action name. */
+		void (*function)(char** argv); /* Function to execute action. */
+	};
+
+	/* Table of supported actions. */
+	static const struct action actions[] = {
+		 {"run", 2, run_task},
+#ifdef FILESYS
+		 {"ls", 1, fsutil_ls},
+		 {"cat", 2, fsutil_cat},
+		 {"rm", 2, fsutil_rm},
+		 {"extract", 1, fsutil_extract},
+		 {"append", 2, fsutil_append},
+#endif
+		 {NULL, 0, NULL},
+	};
+
+	while (*argv != NULL) {
+		const struct action* a;
+		int i;
+
+		/* Find action name. */
+		for (a = actions;; a++)
+			if (a->name == NULL)
+				PANIC("unknown action `%s' (use -h for help)", *argv);
+			else if (!strcmp(*argv, a->name))
+				break;
+
+		/* Check for required arguments. */
+		for (i = 1; i < a->argc; i++)
+			if (argv[i] == NULL)
+				PANIC("action `%s' requires %d argument(s)", *argv, a->argc - 1);
+
+		/* Invoke action and advance. */
+		a->function(argv);
+		argv += a->argc;
+	}
+}
+/* -F: Set timer frequency */
+/* -S: Execute kernel thread slowly */
+/* -tcl: Set limit on threads that can be created */
+/* -fl: Maximum number of pages to put into palloc's free pool */
+
+/* Prints a kernel command line help message and powers off the
+	machine. */
+static void usage(void)
+{
+	printf(
+		 "\nCommand line syntax: [OPTION...] [ACTION...]\n"
+		 "Options must precede actions.\n"
+		 "Actions are executed in the order specified.\n"
+		 "\nAvailable actions:\n"
+#ifdef USERPROG
+		 "  run 'PROG [ARG...]' Run PROG and wait for it to complete.\n"
+#else
+		 "  run TEST           Run TEST.\n"
+#endif
+#ifdef FILESYS
+		 "  ls                 List files in the root directory.\n"
+		 "  cat FILE           Print FILE to the console.\n"
+		 "  rm FILE            Delete FILE.\n"
+		 "Use these actions indirectly via `pintos' -g and -p options:\n"
+		 "  extract            Untar from scratch device into file system.\n"
+		 "  append FILE        Append FILE to tar file on scratch device.\n"
+#endif
+		 "\nOptions:\n"
+		 "  -h                 Print this help message and power off.\n"
+		 "  -q                 Power off VM after actions or on panic.\n"
+		 "  -r                 Reboot after actions.\n"
+		 "  -S                 Execute kernel thread slowly (Debugging).\n"
+#ifdef FILESYS
+		 "  -f                 Format file system device during startup.\n"
+		 "  -filesys=BDEV      Use BDEV for file system instead of default.\n"
+		 "  -scratch=BDEV      Use BDEV for scratch instead of default.\n"
+#ifdef VM
+		 "  -swap=BDEV         Use BDEV for swap instead of default.\n"
+#endif
+#endif
+		 "  -rs=SEED           Set random number seed to SEED.\n"
+		 "  -mlfqs             Use multi-level feedback queue scheduler.\n"
+		 "  -F=FREQ            Set the system timer to FREQ frequency.\n"
+		 "  -tcl=COUNT         Limit the number of threads to COUNT.\n"
+		 "  -fl=COUNT          Limit system memory to COUNT pages.\n"
+#ifdef USERPROG
+		 "  -ul=COUNT          Limit user memory to COUNT pages.\n"
+#endif
+	);
+	shutdown_power_off();
+}
+
+#ifdef FILESYS
+/* Figure out what block devices to cast in the various Pintos roles. */
+static void locate_block_devices(void)
+{
+	locate_block_device(BLOCK_FILESYS, filesys_bdev_name);
+	locate_block_device(BLOCK_SCRATCH, scratch_bdev_name);
+#ifdef VM
+	locate_block_device(BLOCK_SWAP, swap_bdev_name);
+#endif
+}
+
+/* Figures out what block device to use for the given ROLE: the
+	block device with the given NAME, if NAME is non-null,
+	otherwise the first block device in probe order of type
+	ROLE. */
+static void locate_block_device(enum block_type role, const char* name)
+{
+	struct block* block = NULL;
+
+	if (name != NULL) {
+		block = block_get_by_name(name);
+		if (block == NULL)
+			PANIC("No such block device \"%s\"", name);
+	}
+	else {
+		for (block = block_first(); block != NULL; block = block_next(block))
+			if (block_type(block) == role)
+				break;
+	}
+
+	if (block != NULL) {
+		printf("%s: using %s\n", block_type_name(role), block_name(block));
+		block_set_role(role, block);
+	}
+}
+#endif
diff --git a/threads/init.h b/threads/init.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d699f2de701e510a52d23d2590b4b0b199ab559
--- /dev/null
+++ b/threads/init.h
@@ -0,0 +1,14 @@
+#ifndef THREADS_INIT_H
+#define THREADS_INIT_H
+
+#include <debug.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* Page directory with kernel mappings only. */
+extern uint32_t* init_page_dir;
+
+extern int thread_create_limit;
+
+#endif /* threads/init.h */
diff --git a/threads/interrupt.c b/threads/interrupt.c
new file mode 100644
index 0000000000000000000000000000000000000000..04675ece6314ac3e49921b08f7bebbcb045de401
--- /dev/null
+++ b/threads/interrupt.c
@@ -0,0 +1,450 @@
+#include "threads/interrupt.h"
+
+#include "devices/timer.h"
+#include "threads/flags.h"
+#include "threads/intr-stubs.h"
+#include "threads/io.h"
+#include "threads/thread.h"
+#include "threads/vaddr.h"
+
+#include <debug.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+
+/* Programmable Interrupt Controller (PIC) registers.
+	A PC has two PICs, called the master and slave PICs, with the
+	slave attached ("cascaded") to the master IRQ line 2. */
+#define PIC0_CTRL 0x20 /* Master PIC control register address. */
+#define PIC0_DATA 0x21 /* Master PIC data register address. */
+#define PIC1_CTRL 0xa0 /* Slave PIC control register address. */
+#define PIC1_DATA 0xa1 /* Slave PIC data register address. */
+
+/* Number of x86 interrupts. */
+#define INTR_CNT 256
+
+/* The Interrupt Descriptor Table (IDT).  The format is fixed by
+	the CPU.  See [IA32-v3a] sections 5.10 "Interrupt Descriptor
+	Table (IDT)", 5.11 "IDT Descriptors", 5.12.1.2 "Flag Usage By
+	Exception- or Interrupt-Handler Procedure". */
+static uint64_t idt[INTR_CNT];
+
+/* Interrupt handler functions for each interrupt. */
+static intr_handler_func* intr_handlers[INTR_CNT];
+
+/* Names for each interrupt, for debugging purposes. */
+static const char* intr_names[INTR_CNT];
+
+/* Number of unexpected interrupts for each vector.  An
+	unexpected interrupt is one that has no registered handler. */
+static unsigned int unexpected_cnt[INTR_CNT];
+
+/* External interrupts are those generated by devices outside the
+	CPU, such as the timer.  External interrupts run with
+	interrupts turned off, so they never nest, nor are they ever
+	pre-empted.  Handlers for external interrupts also may not
+	sleep, although they may invoke intr_yield_on_return() to
+	request that a new process be scheduled just before the
+	interrupt returns. */
+static bool in_external_intr; /* Are we processing an external interrupt? */
+static bool yield_on_return;	/* Should we yield on interrupt return? */
+
+/* Programmable Interrupt Controller helpers. */
+static void pic_init(void);
+static void pic_end_of_interrupt(int irq);
+
+/* Interrupt Descriptor Table helpers. */
+static uint64_t make_intr_gate(void (*)(void), int dpl);
+static uint64_t make_trap_gate(void (*)(void), int dpl);
+static inline uint64_t make_idtr_operand(uint16_t limit, void* base);
+
+/* Interrupt handlers. */
+void intr_handler(struct intr_frame* args);
+static void unexpected_interrupt(const struct intr_frame*);
+
+/* Returns the current interrupt status. */
+enum intr_level intr_get_level(void)
+{
+	uint32_t flags;
+
+	/* Push the flags register on the processor stack, then pop the
+		value off the stack into `flags'.  See [IA32-v2b] "PUSHF"
+		and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware
+		Interrupts". */
+	asm volatile("pushfl; popl %0" : "=g"(flags));
+
+	return flags & FLAG_IF ? INTR_ON : INTR_OFF;
+}
+
+/* Enables or disables interrupts as specified by LEVEL and
+	returns the previous interrupt status. */
+enum intr_level intr_set_level(enum intr_level level)
+{
+	return level == INTR_ON ? intr_enable() : intr_disable();
+}
+
+/* Enables interrupts and returns the previous interrupt status. */
+enum intr_level intr_enable(void)
+{
+	enum intr_level old_level = intr_get_level();
+	ASSERT(!intr_context());
+
+	/* Enable interrupts by setting the interrupt flag.
+
+		See [IA32-v2b] "STI" and [IA32-v3a] 5.8.1 "Masking Maskable
+		Hardware Interrupts". */
+	asm volatile("sti");
+
+	return old_level;
+}
+
+/* Disables interrupts and returns the previous interrupt status. */
+enum intr_level intr_disable(void)
+{
+	enum intr_level old_level = intr_get_level();
+
+	/* Disable interrupts by clearing the interrupt flag.
+		See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable
+		Hardware Interrupts". */
+	asm volatile("cli" : : : "memory");
+
+	return old_level;
+}
+
+/* Initializes the interrupt system. */
+void intr_init(void)
+{
+	uint64_t idtr_operand;
+	int i;
+
+	/* Initialize interrupt controller. */
+	pic_init();
+
+	/* Initialize IDT. */
+	for (i = 0; i < INTR_CNT; i++) idt[i] = make_intr_gate(intr_stubs[i], 0);
+
+	/* Load IDT register.
+		See [IA32-v2a] "LIDT" and [IA32-v3a] 5.10 "Interrupt
+		Descriptor Table (IDT)". */
+	idtr_operand = make_idtr_operand(sizeof idt - 1, idt);
+	asm volatile("lidt %0" : : "m"(idtr_operand));
+
+	/* Initialize intr_names. */
+	for (i = 0; i < INTR_CNT; i++) intr_names[i] = "unknown";
+	intr_names[0] = "#DE Divide Error";
+	intr_names[1] = "#DB Debug Exception";
+	intr_names[2] = "NMI Interrupt";
+	intr_names[3] = "#BP Breakpoint Exception";
+	intr_names[4] = "#OF Overflow Exception";
+	intr_names[5] = "#BR BOUND Range Exceeded Exception";
+	intr_names[6] = "#UD Invalid Opcode Exception";
+	intr_names[7] = "#NM Device Not Available Exception";
+	intr_names[8] = "#DF Double Fault Exception";
+	intr_names[9] = "Coprocessor Segment Overrun";
+	intr_names[10] = "#TS Invalid TSS Exception";
+	intr_names[11] = "#NP Segment Not Present";
+	intr_names[12] = "#SS Stack Fault Exception";
+	intr_names[13] = "#GP General Protection Exception";
+	intr_names[14] = "#PF Page-Fault Exception";
+	intr_names[16] = "#MF x87 FPU Floating-Point Error";
+	intr_names[17] = "#AC Alignment Check Exception";
+	intr_names[18] = "#MC Machine-Check Exception";
+	intr_names[19] = "#XF SIMD Floating-Point Exception";
+}
+
+/* Registers interrupt VEC_NO to invoke HANDLER with descriptor
+	privilege level DPL.  Names the interrupt NAME for debugging
+	purposes.  The interrupt handler will be invoked with
+	interrupt status set to LEVEL. */
+static void register_handler(
+	 uint8_t vec_no,
+	 int dpl,
+	 enum intr_level level,
+	 intr_handler_func* handler,
+	 const char* name)
+{
+	ASSERT(intr_handlers[vec_no] == NULL);
+	if (level == INTR_ON)
+		idt[vec_no] = make_trap_gate(intr_stubs[vec_no], dpl);
+	else
+		idt[vec_no] = make_intr_gate(intr_stubs[vec_no], dpl);
+	intr_handlers[vec_no] = handler;
+	intr_names[vec_no] = name;
+}
+
+/* Registers external interrupt VEC_NO to invoke HANDLER, which
+	is named NAME for debugging purposes.  The handler will
+	execute with interrupts disabled. */
+void intr_register_ext(uint8_t vec_no, intr_handler_func* handler, const char* name)
+{
+	ASSERT(vec_no >= 0x20 && vec_no <= 0x2f);
+	register_handler(vec_no, 0, INTR_OFF, handler, name);
+}
+
+/* Clear registration for VEC_NO to re-register it later. */
+void intr_clear_int(uint8_t vec_no)
+{
+	ASSERT(vec_no < 0x20 || vec_no > 0x2f);
+	intr_handlers[vec_no] = NULL;
+}
+
+/* Bypass an already existing registration. */
+intr_handler_func* intr_bypass_int(uint8_t vec_no, intr_handler_func* handler)
+{
+	ASSERT(vec_no < 0x20 || vec_no > 0x2f);
+	ASSERT(intr_handlers[vec_no]);
+	intr_handler_func* old = intr_handlers[vec_no];
+	intr_handlers[vec_no] = handler;
+	return old;
+}
+/* Registers internal interrupt VEC_NO to invoke HANDLER, which
+	is named NAME for debugging purposes.  The interrupt handler
+	will be invoked with interrupt status LEVEL.
+
+	The handler will have descriptor privilege level DPL, meaning
+	that it can be invoked intentionally when the processor is in
+	the DPL or lower-numbered ring.  In practice, DPL==3 allows
+	user mode to invoke the interrupts and DPL==0 prevents such
+	invocation.  Faults and exceptions that occur in user mode
+	still cause interrupts with DPL==0 to be invoked.  See
+	[IA32-v3a] sections 4.5 "Privilege Levels" and 4.8.1.1
+	"Accessing Nonconforming Code Segments" for further
+	discussion. */
+void intr_register_int(
+	 uint8_t vec_no,
+	 int dpl,
+	 enum intr_level level,
+	 intr_handler_func* handler,
+	 const char* name)
+{
+	ASSERT(vec_no < 0x20 || vec_no > 0x2f);
+	register_handler(vec_no, dpl, level, handler, name);
+}
+
+/* Returns true during processing of an external interrupt
+	and false at all other times. */
+bool intr_context(void)
+{
+	return in_external_intr;
+}
+
+/* During processing of an external interrupt, directs the
+	interrupt handler to yield to a new process just before
+	returning from the interrupt.  May not be called at any other
+	time. */
+void intr_yield_on_return(void)
+{
+	ASSERT(intr_context());
+	yield_on_return = true;
+}
+
+/* 8259A Programmable Interrupt Controller. */
+
+/* Initializes the PICs.  Refer to [8259A] for details.
+
+	By default, interrupts 0...15 delivered by the PICs will go to
+	interrupt vectors 0...15.  Those vectors are also used for CPU
+	traps and exceptions, so we reprogram the PICs so that
+	interrupts 0...15 are delivered to interrupt vectors 32...47
+	(0x20...0x2f) instead. */
+static void pic_init(void)
+{
+	/* Mask all interrupts on both PICs. */
+	outb(PIC0_DATA, 0xff);
+	outb(PIC1_DATA, 0xff);
+
+	/* Initialize master. */
+	outb(PIC0_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */
+	outb(PIC0_DATA, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */
+	outb(PIC0_DATA, 0x04); /* ICW3: slave PIC on line IR2. */
+	outb(PIC0_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */
+
+	/* Initialize slave. */
+	outb(PIC1_CTRL, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */
+	outb(PIC1_DATA, 0x28); /* ICW2: line IR0...7 -> irq 0x28...0x2f. */
+	outb(PIC1_DATA, 0x02); /* ICW3: slave ID is 2. */
+	outb(PIC1_DATA, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */
+
+	/* Unmask all interrupts. */
+	outb(PIC0_DATA, 0x00);
+	outb(PIC1_DATA, 0x00);
+}
+
+/* Sends an end-of-interrupt signal to the PIC for the given IRQ.
+	If we don't acknowledge the IRQ, it will never be delivered to
+	us again, so this is important.  */
+static void pic_end_of_interrupt(int irq)
+{
+	ASSERT(irq >= 0x20 && irq < 0x30);
+
+	/* Acknowledge master PIC. */
+	outb(0x20, 0x20);
+
+	/* Acknowledge slave PIC if this is a slave interrupt. */
+	if (irq >= 0x28)
+		outb(0xa0, 0x20);
+}
+
+/* Creates an gate that invokes FUNCTION.
+
+	The gate has descriptor privilege level DPL, meaning that it
+	can be invoked intentionally when the processor is in the DPL
+	or lower-numbered ring.  In practice, DPL==3 allows user mode
+	to call into the gate and DPL==0 prevents such calls.  Faults
+	and exceptions that occur in user mode still cause gates with
+	DPL==0 to be invoked.  See [IA32-v3a] sections 4.5 "Privilege
+	Levels" and 4.8.1.1 "Accessing Nonconforming Code Segments"
+	for further discussion.
+
+	TYPE must be either 14 (for an interrupt gate) or 15 (for a
+	trap gate).  The difference is that entering an interrupt gate
+	disables interrupts, but entering a trap gate does not.  See
+	[IA32-v3a] section 5.12.1.2 "Flag Usage By Exception- or
+	Interrupt-Handler Procedure" for discussion. */
+static uint64_t make_gate(void (*function)(void), int dpl, int type)
+{
+	uint32_t e0, e1;
+
+	ASSERT(function != NULL);
+	ASSERT(dpl >= 0 && dpl <= 3);
+	ASSERT(type >= 0 && type <= 15);
+
+	e0
+		 = (((uint32_t) function & 0xffff) /* Offset 15:0. */
+			 | (SEL_KCSEG << 16));			  /* Target code segment. */
+
+	e1
+		 = (((uint32_t) function & 0xffff0000) /* Offset 31:16. */
+			 | (1 << 15)								/* Present. */
+			 | ((uint32_t) dpl << 13)				/* Descriptor privilege level. */
+			 | (0 << 12)								/* System. */
+			 | ((uint32_t) type << 8));			/* Gate type. */
+
+	return e0 | ((uint64_t) e1 << 32);
+}
+
+/* Creates an interrupt gate that invokes FUNCTION with the given
+	DPL. */
+static uint64_t make_intr_gate(void (*function)(void), int dpl)
+{
+	return make_gate(function, dpl, 14);
+}
+
+/* Creates a trap gate that invokes FUNCTION with the given
+	DPL. */
+static uint64_t make_trap_gate(void (*function)(void), int dpl)
+{
+	return make_gate(function, dpl, 15);
+}
+
+/* Returns a descriptor that yields the given LIMIT and BASE when
+	used as an operand for the LIDT instruction. */
+static inline uint64_t make_idtr_operand(uint16_t limit, void* base)
+{
+	return limit | ((uint64_t) (uint32_t) base << 16);
+}
+
+/* Interrupt handlers. */
+
+/* Handler for all interrupts, faults, and exceptions.  This
+	function is called by the assembly language interrupt stubs in
+	intr-stubs.S.  FRAME describes the interrupt and the
+	interrupted thread's registers. */
+void intr_handler(struct intr_frame* frame)
+{
+	bool external;
+	intr_handler_func* handler;
+
+	/* External interrupts are special.
+		We only handle one at a time (so interrupts must be off)
+		and they need to be acknowledged on the PIC (see below).
+		An external interrupt handler cannot sleep. */
+	external = frame->vec_no >= 0x20 && frame->vec_no < 0x30;
+	if (external) {
+		ASSERT(intr_get_level() == INTR_OFF);
+		ASSERT(!intr_context());
+
+		in_external_intr = true;
+		yield_on_return = false;
+	}
+
+	/* Invoke the interrupt's handler. */
+	handler = intr_handlers[frame->vec_no];
+	if (handler != NULL)
+		handler(frame);
+	else if (frame->vec_no == 0x27 || frame->vec_no == 0x2f) {
+		/* There is no handler, but this interrupt can trigger
+			spuriously due to a hardware fault or hardware race
+			condition.  Ignore it. */
+	}
+	else
+		unexpected_interrupt(frame);
+
+	/* Complete the processing of an external interrupt. */
+	if (external) {
+		ASSERT(intr_get_level() == INTR_OFF);
+		ASSERT(intr_context());
+
+		in_external_intr = false;
+		pic_end_of_interrupt(frame->vec_no);
+
+		if (yield_on_return)
+			thread_yield();
+	}
+}
+
+/* Handles an unexpected interrupt with interrupt frame F.  An
+	unexpected interrupt is one that has no registered handler. */
+static void unexpected_interrupt(const struct intr_frame* f)
+{
+	/* Count the number so far. */
+	unsigned int n = ++unexpected_cnt[f->vec_no];
+
+	/* If the number is a power of 2, print a message.  This rate
+		limiting means that we get information about an uncommon
+		unexpected interrupt the first time and fairly often after
+		that, but one that occurs many times will not overwhelm the
+		console. */
+	if ((n & (n - 1)) == 0)
+		printf("Unexpected interrupt %#04x (%s)\n", f->vec_no, intr_names[f->vec_no]);
+}
+
+/* Dumps interrupt frame F to the console, for debugging. */
+void intr_dump_frame(const struct intr_frame* f)
+{
+	uint32_t cr2;
+
+	/* Store current value of CR2 into `cr2'.
+		CR2 is the linear address of the last page fault.
+		See [IA32-v2a] "MOV--Move to/from Control Registers" and
+		[IA32-v3a] 5.14 "Interrupt 14--Page Fault Exception
+		(#PF)". */
+	asm("movl %%cr2, %0" : "=r"(cr2));
+
+	printf("Interrupt %#04x (%s) at eip=%p\n", f->vec_no, intr_names[f->vec_no], f->eip);
+	printf(" cr2=%08" PRIx32 " error=%08" PRIx32 "\n", cr2, f->error_code);
+	printf(
+		 " eax=%08" PRIx32 " ebx=%08" PRIx32 " ecx=%08" PRIx32 " edx=%08" PRIx32 "\n",
+		 f->eax,
+		 f->ebx,
+		 f->ecx,
+		 f->edx);
+	printf(
+		 " esi=%08" PRIx32 " edi=%08" PRIx32 " esp=%08" PRIx32 " ebp=%08" PRIx32 "\n",
+		 f->esi,
+		 f->edi,
+		 (uint32_t) f->esp,
+		 f->ebp);
+	printf(
+		 " cs=%04" PRIx16 " ds=%04" PRIx16 " es=%04" PRIx16 " ss=%04" PRIx16 "\n",
+		 f->cs,
+		 f->ds,
+		 f->es,
+		 f->ss);
+}
+
+/* Returns the name of interrupt VEC. */
+const char* intr_name(uint8_t vec)
+{
+	return intr_names[vec];
+}
diff --git a/threads/interrupt.h b/threads/interrupt.h
new file mode 100644
index 0000000000000000000000000000000000000000..91f7caa65ad224f1421e23c2988130c9f7f72277
--- /dev/null
+++ b/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);
+intr_handler_func* intr_bypass_int(uint8_t vec, intr_handler_func*);
+void intr_clear_int(uint8_t vec);
+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/threads/intr-stubs.S b/threads/intr-stubs.S
new file mode 100644
index 0000000000000000000000000000000000000000..adb674e0f2e36755d95066e2156f53c2876d2ca4
--- /dev/null
+++ b/threads/intr-stubs.S
@@ -0,0 +1,203 @@
+#include "threads/loader.h"
+
+        .text
+
+/* Main interrupt entry point.
+
+   An internal or external interrupt starts in one of the
+   intrNN_stub routines, which push the `struct intr_frame'
+   frame_pointer, error_code, and vec_no members on the stack,
+   then jump here.
+
+   We save the rest of the `struct intr_frame' members to the
+   stack, set up some registers as needed by the kernel, and then
+   call intr_handler(), which actually handles the interrupt.
+
+   We "fall through" to intr_exit to return from the interrupt.
+*/
+.func intr_entry
+intr_entry:
+	/* Save caller's registers. */
+	pushl %ds
+	pushl %es
+	pushl %fs
+	pushl %gs
+	pushal
+        
+	/* Set up kernel environment. */
+	cld			/* String instructions go upward. */
+	mov $SEL_KDSEG, %eax	/* Initialize segment registers. */
+	mov %eax, %ds
+	mov %eax, %es
+	leal 56(%esp), %ebp	/* Set up frame pointer. */
+
+	/* Call interrupt handler. */
+	pushl %esp
+.globl intr_handler
+	call intr_handler
+	addl $4, %esp
+.endfunc
+
+/* Interrupt exit.
+
+   Restores the caller's registers, discards extra data on the
+   stack, and returns to the caller.
+
+   This is a separate function because it is called directly when
+   we launch a new user process (see start_process() in
+   userprog/process.c). */
+.globl intr_exit
+.func intr_exit
+intr_exit:
+        /* Restore caller's registers. */
+	popal
+	popl %gs
+	popl %fs
+	popl %es
+	popl %ds
+
+        /* Discard `struct intr_frame' vec_no, error_code,
+           frame_pointer members. */
+	addl $12, %esp
+
+        /* Return to caller. */
+	iret
+.endfunc
+
+/* Interrupt stubs.
+
+   This defines 256 fragments of code, named `intr00_stub'
+   through `intrff_stub', each of which is used as the entry
+   point for the corresponding interrupt vector.  It also puts
+   the address of each of these functions in the correct spot in
+   `intr_stubs', an array of function pointers.
+
+   Most of the stubs do this:
+
+        1. Push %ebp on the stack (frame_pointer in `struct intr_frame').
+
+        2. Push 0 on the stack (error_code).
+
+        3. Push the interrupt number on the stack (vec_no).
+
+   The CPU pushes an extra "error code" on the stack for a few
+   interrupts.  Because we want %ebp to be where the error code
+   is, we follow a different path:
+
+        1. Push a duplicate copy of the error code on the stack.
+
+        2. Replace the original copy of the error code by %ebp.
+
+        3. Push the interrupt number on the stack. */
+
+	.data
+.globl intr_stubs
+intr_stubs:
+
+/* This implements steps 1 and 2, described above, in the common
+   case where we just push a 0 error code. */
+#define zero                                    \
+	pushl %ebp;                             \
+	pushl $0
+
+/* This implements steps 1 and 2, described above, in the case
+   where the CPU already pushed an error code. */
+#define REAL                                    \
+        pushl (%esp);                           \
+        movl %ebp, 4(%esp)
+
+/* Emits a stub for interrupt vector NUMBER.
+   TYPE is `zero', for the case where we push a 0 error code,
+   or `REAL', if the CPU pushes an error code for us. */
+#define STUB(NUMBER, TYPE)                      \
+	.text;                                  \
+.func intr##NUMBER##_stub;			\
+intr##NUMBER##_stub:                            \
+	TYPE;                                   \
+	push $0x##NUMBER;                       \
+        jmp intr_entry;                         \
+.endfunc;					\
+                                                \
+	.data;                                  \
+	.long intr##NUMBER##_stub;
+
+/* All the stubs. */
+STUB(00, zero) STUB(01, zero) STUB(02, zero) STUB(03, zero)
+STUB(04, zero) STUB(05, zero) STUB(06, zero) STUB(07, zero)
+STUB(08, REAL) STUB(09, zero) STUB(0a, REAL) STUB(0b, REAL)
+STUB(0c, zero) STUB(0d, REAL) STUB(0e, REAL) STUB(0f, zero)
+
+STUB(10, zero) STUB(11, REAL) STUB(12, zero) STUB(13, zero)
+STUB(14, zero) STUB(15, zero) STUB(16, zero) STUB(17, zero)
+STUB(18, REAL) STUB(19, zero) STUB(1a, REAL) STUB(1b, REAL)
+STUB(1c, zero) STUB(1d, REAL) STUB(1e, REAL) STUB(1f, zero)
+
+STUB(20, zero) STUB(21, zero) STUB(22, zero) STUB(23, zero)
+STUB(24, zero) STUB(25, zero) STUB(26, zero) STUB(27, zero)
+STUB(28, zero) STUB(29, zero) STUB(2a, zero) STUB(2b, zero)
+STUB(2c, zero) STUB(2d, zero) STUB(2e, zero) STUB(2f, zero)
+
+STUB(30, zero) STUB(31, zero) STUB(32, zero) STUB(33, zero)
+STUB(34, zero) STUB(35, zero) STUB(36, zero) STUB(37, zero)
+STUB(38, zero) STUB(39, zero) STUB(3a, zero) STUB(3b, zero)
+STUB(3c, zero) STUB(3d, zero) STUB(3e, zero) STUB(3f, zero)
+
+STUB(40, zero) STUB(41, zero) STUB(42, zero) STUB(43, zero)
+STUB(44, zero) STUB(45, zero) STUB(46, zero) STUB(47, zero)
+STUB(48, zero) STUB(49, zero) STUB(4a, zero) STUB(4b, zero)
+STUB(4c, zero) STUB(4d, zero) STUB(4e, zero) STUB(4f, zero)
+
+STUB(50, zero) STUB(51, zero) STUB(52, zero) STUB(53, zero)
+STUB(54, zero) STUB(55, zero) STUB(56, zero) STUB(57, zero)
+STUB(58, zero) STUB(59, zero) STUB(5a, zero) STUB(5b, zero)
+STUB(5c, zero) STUB(5d, zero) STUB(5e, zero) STUB(5f, zero)
+
+STUB(60, zero) STUB(61, zero) STUB(62, zero) STUB(63, zero)
+STUB(64, zero) STUB(65, zero) STUB(66, zero) STUB(67, zero)
+STUB(68, zero) STUB(69, zero) STUB(6a, zero) STUB(6b, zero)
+STUB(6c, zero) STUB(6d, zero) STUB(6e, zero) STUB(6f, zero)
+
+STUB(70, zero) STUB(71, zero) STUB(72, zero) STUB(73, zero)
+STUB(74, zero) STUB(75, zero) STUB(76, zero) STUB(77, zero)
+STUB(78, zero) STUB(79, zero) STUB(7a, zero) STUB(7b, zero)
+STUB(7c, zero) STUB(7d, zero) STUB(7e, zero) STUB(7f, zero)
+
+STUB(80, zero) STUB(81, zero) STUB(82, zero) STUB(83, zero)
+STUB(84, zero) STUB(85, zero) STUB(86, zero) STUB(87, zero)
+STUB(88, zero) STUB(89, zero) STUB(8a, zero) STUB(8b, zero)
+STUB(8c, zero) STUB(8d, zero) STUB(8e, zero) STUB(8f, zero)
+
+STUB(90, zero) STUB(91, zero) STUB(92, zero) STUB(93, zero)
+STUB(94, zero) STUB(95, zero) STUB(96, zero) STUB(97, zero)
+STUB(98, zero) STUB(99, zero) STUB(9a, zero) STUB(9b, zero)
+STUB(9c, zero) STUB(9d, zero) STUB(9e, zero) STUB(9f, zero)
+
+STUB(a0, zero) STUB(a1, zero) STUB(a2, zero) STUB(a3, zero)
+STUB(a4, zero) STUB(a5, zero) STUB(a6, zero) STUB(a7, zero)
+STUB(a8, zero) STUB(a9, zero) STUB(aa, zero) STUB(ab, zero)
+STUB(ac, zero) STUB(ad, zero) STUB(ae, zero) STUB(af, zero)
+
+STUB(b0, zero) STUB(b1, zero) STUB(b2, zero) STUB(b3, zero)
+STUB(b4, zero) STUB(b5, zero) STUB(b6, zero) STUB(b7, zero)
+STUB(b8, zero) STUB(b9, zero) STUB(ba, zero) STUB(bb, zero)
+STUB(bc, zero) STUB(bd, zero) STUB(be, zero) STUB(bf, zero)
+
+STUB(c0, zero) STUB(c1, zero) STUB(c2, zero) STUB(c3, zero)
+STUB(c4, zero) STUB(c5, zero) STUB(c6, zero) STUB(c7, zero)
+STUB(c8, zero) STUB(c9, zero) STUB(ca, zero) STUB(cb, zero)
+STUB(cc, zero) STUB(cd, zero) STUB(ce, zero) STUB(cf, zero)
+
+STUB(d0, zero) STUB(d1, zero) STUB(d2, zero) STUB(d3, zero)
+STUB(d4, zero) STUB(d5, zero) STUB(d6, zero) STUB(d7, zero)
+STUB(d8, zero) STUB(d9, zero) STUB(da, zero) STUB(db, zero)
+STUB(dc, zero) STUB(dd, zero) STUB(de, zero) STUB(df, zero)
+
+STUB(e0, zero) STUB(e1, zero) STUB(e2, zero) STUB(e3, zero)
+STUB(e4, zero) STUB(e5, zero) STUB(e6, zero) STUB(e7, zero)
+STUB(e8, zero) STUB(e9, zero) STUB(ea, zero) STUB(eb, zero)
+STUB(ec, zero) STUB(ed, zero) STUB(ee, zero) STUB(ef, zero)
+
+STUB(f0, zero) STUB(f1, zero) STUB(f2, zero) STUB(f3, zero)
+STUB(f4, zero) STUB(f5, zero) STUB(f6, zero) STUB(f7, zero)
+STUB(f8, zero) STUB(f9, zero) STUB(fa, zero) STUB(fb, zero)
+STUB(fc, zero) STUB(fd, zero) STUB(fe, zero) STUB(ff, zero)
diff --git a/threads/intr-stubs.h b/threads/intr-stubs.h
new file mode 100644
index 0000000000000000000000000000000000000000..b204d54e50eab85cf0f33a1591dc442f4c398c4f
--- /dev/null
+++ b/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/threads/io.h b/threads/io.h
new file mode 100644
index 0000000000000000000000000000000000000000..e3458ec1cb8e961963100d0510c6d91b2cc73122
--- /dev/null
+++ b/threads/io.h
@@ -0,0 +1,103 @@
+#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, %b0" : "=a"(data) : "Nd"(port));
+	return data;
+}
+
+/* Reads CNT bytes from PORT, one after another, and stores them
+	into the buffer starting at ADDR. */
+static inline void insb(uint16_t port, void* addr, size_t cnt)
+{
+	/* See [IA32-v2a] "INS". */
+	asm volatile("rep insb" : "+D"(addr), "+c"(cnt) : "d"(port) : "memory");
+}
+
+/* Reads and returns 16 bits from PORT. */
+static inline uint16_t inw(uint16_t port)
+{
+	uint16_t data;
+	/* See [IA32-v2a] "IN". */
+	asm volatile("inw %w1, %w0" : "=a"(data) : "Nd"(port));
+	return data;
+}
+
+/* Reads CNT 16-bit (halfword) units from PORT, one after
+	another, and stores them into the buffer starting at ADDR. */
+static inline void insw(uint16_t port, void* addr, size_t cnt)
+{
+	/* See [IA32-v2a] "INS". */
+	asm volatile("rep insw" : "+D"(addr), "+c"(cnt) : "d"(port) : "memory");
+}
+
+/* Reads and returns 32 bits from PORT. */
+static inline uint32_t inl(uint16_t port)
+{
+	/* See [IA32-v2a] "IN". */
+	uint32_t data;
+	asm volatile("inl %w1, %0" : "=a"(data) : "Nd"(port));
+	return data;
+}
+
+/* Reads CNT 32-bit (word) units from PORT, one after another,
+	and stores them into the buffer starting at ADDR. */
+static inline void insl(uint16_t port, void* addr, size_t cnt)
+{
+	/* See [IA32-v2a] "INS". */
+	asm volatile("rep insl" : "+D"(addr), "+c"(cnt) : "d"(port) : "memory");
+}
+
+/* Writes byte DATA to PORT. */
+static inline void outb(uint16_t port, uint8_t data)
+{
+	/* See [IA32-v2b] "OUT". */
+	asm volatile("outb %b0, %w1" : : "a"(data), "Nd"(port));
+}
+
+/* Writes to PORT each byte of data in the CNT-byte buffer
+	starting at ADDR. */
+static inline void outsb(uint16_t port, const void* addr, size_t cnt)
+{
+	/* See [IA32-v2b] "OUTS". */
+	asm volatile("rep outsb" : "+S"(addr), "+c"(cnt) : "d"(port));
+}
+
+/* Writes the 16-bit DATA to PORT. */
+static inline void outw(uint16_t port, uint16_t data)
+{
+	/* See [IA32-v2b] "OUT". */
+	asm volatile("outw %w0, %w1" : : "a"(data), "Nd"(port));
+}
+
+/* Writes to PORT each 16-bit unit (halfword) of data in the
+	CNT-halfword buffer starting at ADDR. */
+static inline void outsw(uint16_t port, const void* addr, size_t cnt)
+{
+	/* See [IA32-v2b] "OUTS". */
+	asm volatile("rep outsw" : "+S"(addr), "+c"(cnt) : "d"(port));
+}
+
+/* Writes the 32-bit DATA to PORT. */
+static inline void outl(uint16_t port, uint32_t data)
+{
+	/* See [IA32-v2b] "OUT". */
+	asm volatile("outl %0, %w1" : : "a"(data), "Nd"(port));
+}
+
+/* Writes to PORT each 32-bit unit (word) of data in the CNT-word
+	buffer starting at ADDR. */
+static inline void outsl(uint16_t port, const void* addr, size_t cnt)
+{
+	/* See [IA32-v2b] "OUTS". */
+	asm volatile("rep outsl" : "+S"(addr), "+c"(cnt) : "d"(port));
+}
+
+#endif /* threads/io.h */
diff --git a/threads/kernel.lds.S b/threads/kernel.lds.S
new file mode 100644
index 0000000000000000000000000000000000000000..4840202174311572aa5112d6e6c9d5fcf2429ac4
--- /dev/null
+++ b/threads/kernel.lds.S
@@ -0,0 +1,33 @@
+#include "threads/loader.h"
+
+OUTPUT_FORMAT("elf32-i386")
+OUTPUT_ARCH("i386")
+ENTRY(start)			/* Kernel starts at "start" symbol. */
+SECTIONS
+{
+  /* Specify the kernel base address. */
+  _start = LOADER_PHYS_BASE + LOADER_KERN_BASE;
+
+  /* Make room for the ELF headers. */
+  . = _start + SIZEOF_HEADERS;
+
+  /* Kernel starts with code, followed by read-only data and writable data. */
+  .text : { *(.start) *(.text) } = 0x90
+  .rodata : { *(.rodata) *(.rodata.*) 
+	      . = ALIGN(0x1000); 
+	      _end_kernel_text = .; }
+  .eh_frame : { *(.eh_frame) }
+  .data : { *(.data) 
+	    _signature = .; LONG(0xaa55aa55) }
+
+  .plt : { *(.plt*) }
+
+  /* BSS (zero-initialized data) is after everything else. */
+  _start_bss = .;
+  .bss : { *(.bss) }
+  _end_bss = .;
+
+  _end = .;
+
+  ASSERT (_end - _start <= 512K, "Kernel image is too big.")
+}
diff --git a/threads/loader.S b/threads/loader.S
new file mode 100644
index 0000000000000000000000000000000000000000..dd87ea1c794bc1759ceca654fc953eaea082d763
--- /dev/null
+++ b/threads/loader.S
@@ -0,0 +1,263 @@
+#include "threads/loader.h"
+
+#### Kernel loader.
+
+#### This code should be stored in the first sector of a hard disk.
+#### When the BIOS runs, it loads this code at physical address
+#### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it,
+#### in real mode.  The loader loads the kernel into memory and jumps
+#### to its entry point, which is the start function in start.S.
+####
+#### The BIOS passes in the drive that the loader was read from as
+#### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives
+#### numbered 0x80, 0x81, ...  We want to support booting a kernel on
+#### a different drive from the loader, so we don't take advantage of
+#### this.
+
+# Runs in real mode, which is a 16-bit segment.
+	.code16
+
+# Set up segment registers.
+# Set stack to grow downward from 60 kB (after boot, the kernel
+# continues to use this stack for its initial thread).
+
+	sub %ax, %ax
+	mov %ax, %ds
+	mov %ax, %ss
+	mov $0xf000, %esp
+
+# Configure serial port so we can report progress without connected VGA.
+# See [IntrList] for details.
+	sub %dx, %dx			# Serial port 0.
+	mov $0xe3, %al			# 9600 bps, N-8-1.
+					# AH is already 0 (Initialize Port).
+	int $0x14			# Destroys AX.
+
+	call puts
+	.string "PiLo"
+
+#### Read the partition table on each system hard disk and scan for a
+#### partition of type 0x20, which is the type that we use for a
+#### Pintos kernel.
+####
+#### Read [Partitions] for a description of the partition table format
+#### that we parse.
+####
+#### We print out status messages to show the disk and partition being
+#### scanned, e.g. hda1234 as we scan four partitions on the first
+#### hard disk.
+
+	mov $0x80, %dl			# Hard disk 0.
+read_mbr:
+	sub %ebx, %ebx			# Sector 0.
+	mov $0x2000, %ax		# Use 0x20000 for buffer.
+	mov %ax, %es
+	call read_sector
+	jc no_such_drive
+
+	# Print hd[a-z].
+	call puts
+	.string " hd"
+	mov %dl, %al
+	add $'a' - 0x80, %al
+	call putc
+
+	# Check for MBR signature--if not present, it's not a
+	# partitioned hard disk.
+	cmpw $0xaa55, %es:510
+	jne next_drive
+
+	mov $446, %si			# Offset of partition table entry 1.
+	mov $'1', %al
+check_partition:
+	# Is it an unused partition?
+	cmpl $0, %es:(%si)
+	je next_partition
+
+	# Print [1-4].
+	call putc
+
+	# Is it a Pintos kernel partition?
+	cmpb $0x20, %es:4(%si)
+	jne next_partition
+
+	# Is it a bootable partition?
+	cmpb $0x80, %es:(%si)
+	je load_kernel
+
+next_partition:
+	# No match for this partition, go on to the next one.
+	add $16, %si			# Offset to next partition table entry.
+	inc %al
+	cmp $510, %si
+	jb check_partition
+
+next_drive:
+	# No match on this drive, go on to the next one.
+	inc %dl
+	jnc read_mbr
+
+no_such_drive:
+no_boot_partition:
+	# Didn't find a Pintos kernel partition anywhere, give up.
+	call puts
+	.string "\rNot found\r"
+
+	# Notify BIOS that boot failed.  See [IntrList].
+	int $0x18
+
+#### We found a kernel.  The kernel's drive is in DL.  The partition
+#### table entry for the kernel's partition is at ES:SI.  Our job now
+#### is to read the kernel from disk and jump to its start address.
+
+load_kernel:
+	call puts
+	.string "\rLoading"
+
+	# Figure out number of sectors to read.  A Pintos kernel is
+	# just an ELF format object, which doesn't have an
+	# easy-to-read field to identify its own size (see [ELF1]).
+	# But we limit Pintos kernels to 512 kB for other reasons, so
+	# it's easy enough to just read the entire contents of the
+	# partition or 512 kB from disk, whichever is smaller.
+	mov %es:12(%si), %ecx		# EBP = number of sectors
+	cmp $1024, %ecx			# Cap size at 512 kB
+	jbe 1f
+	mov $1024, %cx
+1:
+
+	mov %es:8(%si), %ebx		# EBX = first sector
+	mov $0x2000, %ax		# Start load address: 0x20000
+
+next_sector:
+	# Read one sector into memory.
+	mov %ax, %es			# ES:0000 -> load address
+	call read_sector
+	jc read_failed
+
+	# Print '.' as progress indicator once every 16 sectors == 8 kB.
+	test $15, %bl
+	jnz 1f
+	call puts
+	.string "."
+1:
+
+	# Advance memory pointer and disk sector.
+	add $0x20, %ax
+	inc %bx
+	loop next_sector
+
+	call puts
+	.string "\r"
+
+#### Transfer control to the kernel that we loaded.  We read the start
+#### address out of the ELF header (see [ELF1]) and convert it from a
+#### 32-bit linear address into a 16:16 segment:offset address for
+#### real mode, then jump to the converted address.  The 80x86 doesn't
+#### have an instruction to jump to an absolute segment:offset kept in
+#### registers, so in fact we store the address in a temporary memory
+#### location, then jump indirectly through that location.  To save 4
+#### bytes in the loader, we reuse 4 bytes of the loader's code for
+#### this temporary pointer.
+
+	mov $0x2000, %ax
+	mov %ax, %es
+	mov %es:0x18, %dx
+	mov %dx, start
+	movw $0x2000, start + 2
+	ljmp *start
+
+read_failed:
+start:
+	# Disk sector read failed.
+	call puts
+1:	.string "\rBad read\r"
+
+	# Notify BIOS that boot failed.  See [IntrList].
+	int $0x18
+
+#### Print string subroutine.  To save space in the loader, this
+#### subroutine takes its null-terminated string argument from the
+#### code stream just after the call, and then returns to the byte
+#### just after the terminating null.  This subroutine preserves all
+#### general-purpose registers.
+
+puts:	xchg %si, %ss:(%esp)
+	push %ax
+next_char:
+	mov %cs:(%si), %al
+	inc %si
+	test %al, %al
+	jz 1f
+	call putc
+	jmp next_char
+1:	pop %ax
+	xchg %si, %ss:(%esp)
+	ret
+
+#### Character output subroutine.  Prints the character in AL to the
+#### VGA display and serial port 0, using BIOS services (see
+#### [IntrList]).  Preserves all general-purpose registers.
+####
+#### If called upon to output a carriage return, this subroutine
+#### automatically supplies the following line feed.
+
+putc:	pusha
+
+1:	sub %bh, %bh			# Page 0.
+	mov $0x0e, %ah			# Teletype output service.
+	int $0x10
+
+	mov $0x01, %ah			# Serial port output service.
+	sub %dx, %dx			# Serial port 0.
+2:	int $0x14			# Destroys AH.
+	test $0x80, %ah			# Output timed out?
+	jz 3f
+	movw $0x9090, 2b		# Turn "int $0x14" above into NOPs.
+
+3:
+	cmp $'\r', %al
+	jne popa_ret
+	mov $'\n', %al
+	jmp 1b
+
+#### Sector read subroutine.  Takes a drive number in DL (0x80 = hard
+#### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and
+#### reads the specified sector into memory at ES:0000.  Returns with
+#### carry set on error, clear otherwise.  Preserves all
+#### general-purpose registers.
+
+read_sector:
+	pusha
+	sub %ax, %ax
+	push %ax			# LBA sector number [48:63]
+	push %ax			# LBA sector number [32:47]
+	push %ebx			# LBA sector number [0:31]
+	push %es			# Buffer segment
+	push %ax			# Buffer offset (always 0)
+	push $1				# Number of sectors to read
+	push $16			# Packet size
+	mov $0x42, %ah			# Extended read
+	mov %sp, %si			# DS:SI -> packet
+	int $0x13			# Error code in CF
+	popa				# Pop 16 bytes, preserve flags
+popa_ret:
+	popa
+	ret				# Error code still in CF
+
+#### Command-line arguments and their count.
+#### This is written by the `pintos' utility and read by the kernel.
+#### The loader itself does not do anything with the command line.
+	.org LOADER_ARG_CNT - LOADER_BASE
+	.fill LOADER_ARG_CNT_LEN, 1, 0
+
+	.org LOADER_ARGS - LOADER_BASE
+	.fill LOADER_ARGS_LEN, 1, 0
+
+#### Partition table.
+	.org LOADER_PARTS - LOADER_BASE
+	.fill LOADER_PARTS_LEN, 1, 0
+
+#### Boot-sector signature for BIOS inspection.
+	.org LOADER_SIG - LOADER_BASE
+	.word 0xaa55
diff --git a/threads/loader.h b/threads/loader.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7d494617a2aa8098ade463987e7303346a4053c
--- /dev/null
+++ b/threads/loader.h
@@ -0,0 +1,40 @@
+#ifndef THREADS_LOADER_H
+#define THREADS_LOADER_H
+
+/* Constants fixed by the PC BIOS. */
+#define LOADER_BASE 0x7c00 /* Physical address of loader's base. */
+#define LOADER_END  0x7e00 /* Physical address of end of loader. */
+
+/* Physical address of kernel base. */
+#define LOADER_KERN_BASE 0x20000 /* 128 kB. */
+
+/* Kernel virtual address at which all physical memory is mapped.
+	Must be aligned on a 4 MB boundary. */
+#define LOADER_PHYS_BASE 0xc0000000 /* 3 GB. */
+
+/* Important loader physical addresses. */
+#define LOADER_SIG	  (LOADER_END - LOADER_SIG_LEN)		 /* 0xaa55 BIOS signature. */
+#define LOADER_PARTS	  (LOADER_SIG - LOADER_PARTS_LEN)	 /* Partition table. */
+#define LOADER_ARGS	  (LOADER_PARTS - LOADER_ARGS_LEN)	 /* Command-line args. */
+#define LOADER_ARG_CNT (LOADER_ARGS - LOADER_ARG_CNT_LEN) /* Number of args. */
+
+/* Sizes of loader data structures. */
+#define LOADER_SIG_LEN		2
+#define LOADER_PARTS_LEN	64
+#define LOADER_ARGS_LEN		128
+#define LOADER_ARG_CNT_LEN 4
+
+/* GDT selectors defined by loader.
+	More selectors are defined by userprog/gdt.h. */
+#define SEL_NULL	0x00 /* Null selector. */
+#define SEL_KCSEG 0x08 /* Kernel code selector. */
+#define SEL_KDSEG 0x10 /* Kernel data selector. */
+
+#ifndef __ASSEMBLER__
+#include <stdint.h>
+
+/* Amount of physical memory, in 4 kB pages. */
+extern uint32_t init_ram_pages;
+#endif
+
+#endif /* threads/loader.h */
diff --git a/threads/malloc.c b/threads/malloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..77a8455fd5586bf5ef3545bf3a0757857d25bec3
--- /dev/null
+++ b/threads/malloc.c
@@ -0,0 +1,269 @@
+#include "threads/malloc.h"
+
+#include "threads/palloc.h"
+#include "threads/synch.h"
+#include "threads/vaddr.h"
+
+#include <debug.h>
+#include <list.h>
+#include <round.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.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/threads/malloc.h b/threads/malloc.h
new file mode 100644
index 0000000000000000000000000000000000000000..7a82708a678150aecad764c35fa2dddcca228be5
--- /dev/null
+++ b/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/threads/palloc.c b/threads/palloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..b9828f0dc132b08ef38a45740842294d01870aa8
--- /dev/null
+++ b/threads/palloc.c
@@ -0,0 +1,174 @@
+#include "threads/palloc.h"
+
+#include "threads/loader.h"
+#include "threads/synch.h"
+#include "threads/vaddr.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>
+
+/* Page allocator.  Hands out memory in page-size (or
+	page-multiple) chunks.  See malloc.h for an allocator that
+	hands out smaller chunks.
+
+	System memory is divided into two "pools" called the kernel
+	and user pools.  The user pool is for user (virtual) memory
+	pages, the kernel pool for everything else.  The idea here is
+	that the kernel needs to have memory for its own operations
+	even if user processes are swapping like mad.
+
+	By default, half of system RAM is given to the kernel pool and
+	half to the user pool.  That should be huge overkill for the
+	kernel pool, but that's just fine for demonstration purposes. */
+
+/* A memory pool. */
+struct pool {
+	struct lock lock;			 /* Mutual exclusion. */
+	struct bitmap* used_map; /* Bitmap of free pages. */
+	uint8_t* base;				 /* Base of pool. */
+};
+
+/* Two pools: one for kernel data, one for user pages. */
+static struct pool kernel_pool, user_pool;
+
+static void init_pool(struct pool*, void* base, size_t page_cnt, const char* name);
+static bool page_from_pool(const struct pool*, void* page);
+
+/* Initializes the page allocator.  At most USER_PAGE_LIMIT
+	pages are put into the user pool. */
+void palloc_init(size_t user_page_limit, size_t free_page_limit)
+{
+	/* Free memory starts at 1 MB and runs to the end of RAM. */
+	uint8_t* free_start = ptov(1024 * 1024);
+	uint8_t* free_end = ptov(init_ram_pages * PGSIZE);
+	size_t free_pages = (free_end - free_start) / PGSIZE;
+	size_t user_pages = free_pages / 2;
+	size_t kernel_pages;
+	if (free_pages > free_page_limit)
+		free_pages = free_page_limit;
+	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/threads/palloc.h b/threads/palloc.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e5593379b95b7561301d267db894c62ecb82958
--- /dev/null
+++ b/threads/palloc.h
@@ -0,0 +1,19 @@
+#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. */
+};
+
+void palloc_init(size_t user_page_limit, size_t free_page_limit);
+void* palloc_get_page(enum palloc_flags);
+void* palloc_get_multiple(enum palloc_flags, size_t page_cnt);
+void palloc_free_page(void*);
+void palloc_free_multiple(void*, size_t page_cnt);
+
+#endif /* threads/palloc.h */
diff --git a/threads/pte.h b/threads/pte.h
new file mode 100644
index 0000000000000000000000000000000000000000..3ee40892f6ac74945b5062f0b5f4fec5fe11b6e5
--- /dev/null
+++ b/threads/pte.h
@@ -0,0 +1,113 @@
+#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/threads/start.S b/threads/start.S
new file mode 100644
index 0000000000000000000000000000000000000000..29ffa7a42f3ed45a948befce9bceb9bd3c518ff5
--- /dev/null
+++ b/threads/start.S
@@ -0,0 +1,204 @@
+	#include "threads/loader.h"
+
+#### Kernel startup code.
+
+#### The loader (in loader.S) loads the kernel at physical address
+#### 0x20000 (128 kB) and jumps to "start", defined here.  This code
+#### switches from real mode to 32-bit protected mode and calls
+#### main().
+
+/* Flags in control register 0. */
+#define CR0_PE 0x00000001      /* Protection Enable. */
+#define CR0_EM 0x00000004      /* (Floating-point) Emulation. */
+#define CR0_PG 0x80000000      /* Paging. */
+#define CR0_WP 0x00010000      /* Write-Protect enable in kernel mode. */
+
+	.section .start
+
+# The following code runs in real mode, which is a 16-bit code segment.
+	.code16
+
+.func start
+.globl start
+start:
+
+# The loader called into us with CS = 0x2000, SS = 0x0000, ESP = 0xf000,
+# but we should initialize the other segment registers.
+
+	mov $0x2000, %ax
+	mov %ax, %ds
+	mov %ax, %es
+
+# Set string instructions to go upward.
+	cld
+
+#### Get memory size, via interrupt 15h function 88h (see [IntrList]),
+#### which returns AX = (kB of physical memory) - 1024.  This only
+#### works for memory sizes <= 65 MB, which should be fine for our
+#### purposes.  We cap memory at 64 MB because that's all we prepare
+#### page tables for, below.
+
+	movb $0x88, %ah
+	int $0x15
+	addl $1024, %eax	# Total kB memory
+	cmp $0x10000, %eax	# Cap at 64 MB
+	jbe 1f
+	mov $0x10000, %eax
+1:	shrl $2, %eax		# Total 4 kB pages
+	addr32 movl %eax, init_ram_pages - LOADER_PHYS_BASE - 0x20000
+
+#### Enable A20.  Address line 20 is tied low when the machine boots,
+#### which prevents addressing memory about 1 MB.  This code fixes it.
+
+# Poll status register while busy.
+
+1:	inb $0x64, %al
+	testb $0x2, %al
+	jnz 1b
+
+# Send command for writing output port.
+
+	movb $0xd1, %al
+	outb %al, $0x64
+
+# Poll status register while busy.
+
+1:	inb $0x64, %al
+	testb $0x2, %al
+	jnz 1b
+
+# Enable A20 line.
+
+	movb $0xdf, %al
+	outb %al, $0x60
+
+# Poll status register while busy.
+
+1:	inb $0x64, %al
+	testb $0x2, %al
+	jnz 1b
+
+#### Create temporary page directory and page table and set page
+#### directory base register.
+
+# Create page directory at 0xf000 (60 kB) and fill with zeroes.
+	mov $0xf00, %ax
+	mov %ax, %es
+	subl %eax, %eax
+	subl %edi, %edi
+	movl $0x400, %ecx
+	rep stosl
+
+# Add PDEs to point to page tables for the first 64 MB of RAM.
+# Also add identical PDEs starting at LOADER_PHYS_BASE.
+# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
+# for a description of the bits in %eax.
+
+	movl $0x10007, %eax
+	movl $0x11, %ecx
+	subl %edi, %edi
+1:	movl %eax, %es:(%di)
+	movl %eax, %es:LOADER_PHYS_BASE >> 20(%di)
+	addw $4, %di
+	addl $0x1000, %eax
+	loop 1b
+
+# Set up page tables for one-to-map linear to physical map for the
+# first 64 MB of RAM.
+# See [IA32-v3a] section 3.7.6 "Page-Directory and Page-Table Entries"
+# for a description of the bits in %eax.
+
+	movw $0x1000, %ax
+	movw %ax, %es
+	movl $0x7, %eax
+	movl $0x4000, %ecx
+	subl %edi, %edi
+1:	movl %eax, %es:(%di)
+	addw $4, %di
+	addl $0x1000, %eax
+	loop 1b
+
+# Set page directory base register.
+
+	movl $0xf000, %eax
+	movl %eax, %cr3
+
+#### Switch to protected mode.
+
+# First, disable interrupts.  We won't set up the IDT until we get
+# into C code, so any interrupt would blow us away.
+
+	cli
+
+# Protected mode requires a GDT, so point the GDTR to our GDT.
+# We need a data32 prefix to ensure that all 32 bits of the GDT
+# descriptor are loaded (default is to load only 24 bits).
+# The CPU doesn't need an addr32 prefix but ELF doesn't do 16-bit
+# relocations.
+
+	data32 addr32 lgdt gdtdesc - LOADER_PHYS_BASE - 0x20000
+
+# Then we turn on the following bits in CR0:
+#    PE (Protect Enable): this turns on protected mode.
+#    PG (Paging): turns on paging.
+#    WP (Write Protect): if unset, ring 0 code ignores
+#       write-protect bits in page tables (!).
+#    EM (Emulation): forces floating-point instructions to trap.
+#       We don't support floating point.
+
+	movl %cr0, %eax
+	orl $CR0_PE | CR0_PG | CR0_WP | CR0_EM, %eax
+	movl %eax, %cr0
+
+# We're now in protected mode in a 16-bit segment.  The CPU still has
+# the real-mode code segment cached in %cs's segment descriptor.  We
+# need to reload %cs, and the easiest way is to use a far jump.
+# Because we're not running in a 32-bit segment the data32 prefix is
+# needed to jump to a 32-bit offset in the target segment.
+
+	data32 ljmp $SEL_KCSEG, $1f
+
+# We're now in protected mode in a 32-bit segment.
+# Let the assembler know.
+
+	.code32
+
+# Reload all the other segment registers and the stack pointer to
+# point into our new GDT.
+
+1:	mov $SEL_KDSEG, %ax
+	mov %ax, %ds
+	mov %ax, %es
+	mov %ax, %fs
+	mov %ax, %gs
+	mov %ax, %ss
+	addl $LOADER_PHYS_BASE, %esp
+	movl $0, %ebp			# Null-terminate main()'s backtrace
+
+#### Call main().
+
+	call main
+
+# main() shouldn't ever return.  If it does, spin.
+
+1:	jmp 1b
+.endfunc
+
+#### GDT
+
+	.align 8
+gdt:
+	.quad 0x0000000000000000	# Null segment.  Not used by CPU.
+	.quad 0x00cf9a000000ffff	# System code, base 0, limit 4 GB.
+	.quad 0x00cf92000000ffff        # System data, base 0, limit 4 GB.
+
+gdtdesc:
+	.word	gdtdesc - gdt - 1	# Size of the GDT, minus 1 byte.
+	.long	gdt			# Address of the GDT.
+
+#### Physical memory size in 4 kB pages.  This is exported to the rest
+#### of the kernel.
+.globl init_ram_pages
+init_ram_pages:
+	.long 0
+
diff --git a/threads/switch.S b/threads/switch.S
new file mode 100644
index 0000000000000000000000000000000000000000..b511d535f0ce0617336952f9bfc833e28b5b32f8
--- /dev/null
+++ b/threads/switch.S
@@ -0,0 +1,77 @@
+#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
+    pushfl
+
+	# 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
+
+    # Copy parts of the current flags over to the old state. Only
+	# preserve the state of the 0x0100 bit. This is likely overkill.
+	pushfl
+	popl %edi
+	andl $0xFFFFFEFF, %edi
+	popl %esi
+	orl %esi, %edi
+	pushl %edi
+
+	# Restore caller's register state.
+    popfl
+	# Restore caller's register state.
+	popl %edi
+	popl %esi
+	popl %ebp
+	popl %ebx
+        ret
+.endfunc
+
+.globl switch_entry
+.func switch_entry
+switch_entry:
+	# Discard switch_threads() arguments.
+	addl $8, %esp
+
+	# Call thread_schedule_tail(prev).
+	pushl %eax
+.globl thread_schedule_tail
+	call thread_schedule_tail
+	addl $4, %esp
+
+	# Start thread proper.
+	ret
+.endfunc
diff --git a/threads/switch.h b/threads/switch.h
new file mode 100644
index 0000000000000000000000000000000000000000..2827913db80f4f582793925bd3b30e8073f54998
--- /dev/null
+++ b/threads/switch.h
@@ -0,0 +1,38 @@
+#ifndef THREADS_SWITCH_H
+#define THREADS_SWITCH_H
+
+#ifndef __ASSEMBLER__
+/* switch_thread()'s stack frame. */
+struct switch_threads_frame {
+	uint32_t eflags;		/*  0: Saved %eflags. */
+	uint32_t edi;			/*  4: Saved %edi. */
+	uint32_t esi;			/*  8: Saved %esi. */
+	uint32_t ebp;			/*  12: Saved %ebp. */
+	uint32_t ebx;			/* 16: Saved %ebx. */
+	void (*eip)(void);	/* 20: Return address. */
+	struct thread* cur;	/* 24: switch_threads()'s CUR argument. */
+	struct thread* next; /* 28: 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  24
+#define SWITCH_NEXT 28
+
+#endif /* threads/switch.h */
diff --git a/threads/synch.c b/threads/synch.c
new file mode 100644
index 0000000000000000000000000000000000000000..072b02a54cee11c1be5a692322d8185685ef5df8
--- /dev/null
+++ b/threads/synch.c
@@ -0,0 +1,318 @@
+/* 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 "threads/interrupt.h"
+#include "threads/thread.h"
+
+#include <stdio.h>
+#include <string.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/threads/synch.h b/threads/synch.h
new file mode 100644
index 0000000000000000000000000000000000000000..30fd86d0ab6fc177893e74a89aa514e4aa0bacff
--- /dev/null
+++ b/threads/synch.h
@@ -0,0 +1,48 @@
+#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/threads/thread.c b/threads/thread.c
new file mode 100644
index 0000000000000000000000000000000000000000..c75991ecdd0dc2b7855dcb71df89d9dc86c1802e
--- /dev/null
+++ b/threads/thread.c
@@ -0,0 +1,582 @@
+#include "threads/thread.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"
+
+#include <debug.h>
+#include <random.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+//#include "userprog/process.h"
+
+#ifdef USERPROG
+#include "userprog/process.h"
+#endif
+
+/* Random value for struct thread's `magic' member.
+	Used to detect stack overflow.  See the big comment at the top
+	of thread.h for details. */
+#define THREAD_MAGIC 0xcd6abf4b
+
+/* List of processes in THREAD_READY state, that is, processes
+	that are ready to run but not actually running. */
+static struct list ready_list;
+
+/* List of all processes.  Processes are added to this list
+	when they are first scheduled and removed when they exit. */
+static struct list all_list;
+
+
+
+
+
+
+/* Idle thread. */
+static struct thread* idle_thread;
+
+/* Initial thread, the thread running init.c:main(). */
+static struct thread* initial_thread;
+
+/* Lock used by allocate_tid(). */
+static struct lock tid_lock;
+
+/* Stack frame for kernel_thread(). */
+struct kernel_thread_frame {
+	void* eip;				  /* Return address. */
+	thread_func* function; /* Function to call. */
+	void * aux; 		  /* Auxiliary data for function. */
+};
+
+/* Statistics. */
+static long long idle_ticks;	 /* # of timer ticks spent idle. */
+static long long kernel_ticks; /* # of timer ticks in kernel threads. */
+static long long user_ticks;	 /* # of timer ticks in user programs. */
+
+/* Scheduling. */
+#define TIME_SLICE 4				/* # of timer ticks to give each thread. */
+static unsigned thread_ticks; /* # of timer ticks since last yield. */
+
+/* If false (default), use round-robin scheduler.
+	If true, use multi-level feedback queue scheduler.
+	Controlled by kernel command-line option "-o mlfqs". */
+bool thread_mlfqs;
+
+static void kernel_thread(thread_func*, void* aux);
+
+static void idle(void* aux UNUSED);
+static struct thread* running_thread(void);
+static struct thread* next_thread_to_run(void);
+static void init_thread(struct thread*, const char* name, int priority);
+static bool is_thread(struct thread*) UNUSED;
+static void* alloc_frame(struct thread*, size_t size);
+static void schedule(void);
+void thread_schedule_tail(struct thread* prev);
+static tid_t allocate_tid(void);
+
+/* Initializes the threading system by transforming the code
+	that's currently running into a thread.  This can't work in
+	general and it is possible in this case only because loader.S
+	was careful to put the bottom of the stack at a page boundary.
+
+	Also initializes the run queue and the tid lock.
+
+	After calling this function, be sure to initialize the page
+	allocator before trying to create any threads with
+	thread_create().
+
+	It is not safe to call thread_current() until this function
+	finishes. */
+void thread_init(void)
+{
+	ASSERT(intr_get_level() == INTR_OFF);
+
+	lock_init(&tid_lock);
+	list_init(&ready_list);
+	list_init(&all_list);
+
+	
+
+	/* Set up a thread structure for the running thread. */
+	initial_thread = running_thread();
+	init_thread(initial_thread, "main", PRI_DEFAULT);
+	initial_thread->status = THREAD_RUNNING;
+	initial_thread->tid = allocate_tid();
+
+}
+
+/* Starts preemptive thread scheduling by enabling interrupts.
+	Also creates the idle thread. */
+void thread_start(void)
+{
+	/* Create the idle thread. */
+	struct semaphore idle_started;
+	sema_init(&idle_started, 0);
+	thread_create("idle", PRI_MIN, idle, &idle_started);
+
+	/* Start preemptive thread scheduling. */
+	intr_enable();
+
+	/* Wait for the idle thread to initialize idle_thread. */
+	sema_down(&idle_started);
+}
+
+/* Called by the timer interrupt handler at each timer tick.
+	Thus, this function runs in an external interrupt context. */
+void thread_tick(void)
+{
+	struct thread* t = thread_current();
+
+	/* Update statistics. */
+	if (t == idle_thread)
+		idle_ticks++;
+#ifdef USERPROG
+	else if (t->pagedir != NULL)
+		user_ticks++;
+#endif
+	else
+		kernel_ticks++;
+
+	/* Enforce preemption. */
+	if (++thread_ticks >= TIME_SLICE)
+		intr_yield_on_return();
+}
+
+/* Prints thread statistics. */
+void thread_print_stats(void)
+{
+	printf(
+		 "Thread: %lld idle ticks, %lld kernel ticks, %lld user ticks\n",
+		 idle_ticks,
+		 kernel_ticks,
+		 user_ticks);
+}
+
+/* Creates a new kernel thread named NAME with the given initial
+	PRIORITY, which executes FUNCTION passing AUX as the argument,
+	and adds it to the ready queue.  Returns the thread identifier
+	for the new thread, or TID_ERROR if creation fails.
+
+	If thread_start() has been called, then the new thread may be
+	scheduled before thread_create() returns.  It could even exit
+	before thread_create() returns.  Contrariwise, the original
+	thread may run for any amount of time before the new thread is
+	scheduled.  Use a semaphore or some other form of
+	synchronization if you need to ensure ordering.
+
+	The code provided sets the new thread's `priority' member to
+	PRIORITY, but no actual priority scheduling is implemented.
+	Priority scheduling is the goal of Problem 1-3. */
+tid_t thread_create(const char* name, int priority, thread_func* function, void* aux)
+{
+	struct thread* t;
+	struct kernel_thread_frame* kf;
+	struct switch_entry_frame* ef;
+	struct switch_threads_frame* sf;
+	tid_t tid;
+
+
+
+	ASSERT(function != NULL);
+	/////////////////////////////////////////////////
+	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;
+	sf->ebp = 0;
+
+	/* Add to run queue. */
+	thread_unblock(t);
+
+	return tid;
+
+}
+
+/* Puts the current thread to sleep.  It will not be scheduled
+	again until awoken by thread_unblock().
+
+	This function must be called with interrupts turned off.  It
+	is usually a better idea to use one of the synchronization
+	primitives in synch.h. */
+void thread_block(void)
+{
+	ASSERT(!intr_context());
+	ASSERT(intr_get_level() == INTR_OFF);
+
+	thread_current()->status = THREAD_BLOCKED;
+	schedule();
+}
+
+/* Transitions a blocked thread T to the ready-to-run state.
+	This is an error if T is not blocked.  (Use thread_yield() to
+	make the running thread ready.)
+
+	This function does not preempt the running thread.  This can
+	be important: if the caller had disabled interrupts itself,
+	it may expect that it can atomically unblock a thread and
+	update other data. */
+void thread_unblock(struct thread* t)
+{
+	enum intr_level old_level;
+
+	ASSERT(is_thread(t));
+
+	old_level = intr_disable();
+	ASSERT(t->status == THREAD_BLOCKED);
+	list_push_back(&ready_list, &t->elem);
+	t->status = THREAD_READY;
+	intr_set_level(old_level);
+}
+
+/* Returns the name of the running thread. */
+const char* thread_name(void)
+{
+	return thread_current()->name;
+}
+
+/* Returns the running thread.
+	This is running_thread() plus a couple of sanity checks.
+	See the big comment at the top of thread.h for details. */
+struct thread* thread_current(void)
+{
+	struct thread* t = running_thread();
+
+	/* Make sure T is really a thread.
+		If either of these assertions fire, then your thread may
+		have overflowed its stack.  Each thread has less than 4 kB
+		of stack, so a few big automatic arrays or moderate
+		recursion can cause stack overflow. */
+	ASSERT(is_thread(t));
+	ASSERT(t->status == THREAD_RUNNING);
+
+	return t;
+}
+
+/* Returns the running thread's tid. */
+tid_t thread_tid(void)
+{
+	return thread_current()->tid;
+}
+
+/* Deschedules the current thread and destroys it.  Never
+	returns to the caller. */
+void thread_exit(void)
+{
+	ASSERT(!intr_context());
+
+#ifdef USERPROG
+	process_exit();
+#endif
+
+	/* Remove thread from all threads list, set our status to dying,
+		and schedule another process.  That process will destroy us
+		when it calls thread_schedule_tail(). */
+	intr_disable();
+	list_remove(&thread_current()->allelem);
+	thread_current()->status = THREAD_DYING;
+	schedule();
+	NOT_REACHED();
+}
+
+/* Yields the CPU.  The current thread is not put to sleep and
+	may be scheduled again immediately at the scheduler's whim. */
+void thread_yield(void)
+{
+	struct thread* cur = thread_current();
+	enum intr_level old_level;
+
+	ASSERT(!intr_context());
+
+	old_level = intr_disable();
+	if (cur != idle_thread)
+		list_push_back(&ready_list, &cur->elem);
+	cur->status = THREAD_READY;
+	schedule();
+	intr_set_level(old_level);
+}
+
+/* Invoke function 'func' on all threads, passing along 'aux'.
+	This function must be called with interrupts off. */
+void thread_foreach(thread_action_func* func, void* aux)
+{
+	struct list_elem* e;
+
+	ASSERT(intr_get_level() == INTR_OFF);
+
+	for (e = list_begin(&all_list); e != list_end(&all_list); e = list_next(e)) {
+		struct thread* t = list_entry(e, struct thread, allelem);
+		func(t, aux);
+	}
+}
+
+/* Sets the current thread's priority to NEW_PRIORITY. */
+void thread_set_priority(int new_priority)
+{
+	thread_current()->priority = new_priority;
+}
+
+/* Returns the current thread's priority. */
+int thread_get_priority(void)
+{
+	return thread_current()->priority;
+}
+
+/* Sets the current thread's nice value to NICE. */
+void thread_set_nice(int nice UNUSED)
+{
+	/* Not yet implemented. */
+}
+
+/* Returns the current thread's nice value. */
+int thread_get_nice(void)
+{
+	/* Not yet implemented. */
+	return 0;
+}
+
+/* Returns 100 times the system load average. */
+int thread_get_load_avg(void)
+{
+	/* Not yet implemented. */
+	return 0;
+}
+
+/* Returns 100 times the current thread's recent_cpu value. */
+int thread_get_recent_cpu(void)
+{
+	/* Not yet implemented. */
+	return 0;
+}
+
+/* Idle thread.  Executes when no other thread is ready to run.
+
+	The idle thread is initially put on the ready list by
+	thread_start().  It will be scheduled once initially, at which
+	point it initializes idle_thread, "up"s the semaphore passed
+	to it to enable thread_start() to continue, and immediately
+	blocks.  After that, the idle thread never appears in the
+	ready list.  It is returned by next_thread_to_run() as a
+	special case when the ready list is empty. */
+static void idle(void* idle_started_ UNUSED)
+{
+	struct semaphore* idle_started = idle_started_;
+	idle_thread = thread_current();
+	sema_up(idle_started);
+
+	for (;;) {
+		/* Let someone else run. */
+		intr_disable();
+		thread_block();
+
+		/* Re-enable interrupts and wait for the next one.
+
+			The `sti' instruction disables interrupts until the
+			completion of the next instruction, so these two
+			instructions are executed atomically.  This atomicity is
+			important; otherwise, an interrupt could be handled
+			between re-enabling interrupts and waiting for the next
+			one to occur, wasting as much as one clock tick worth of
+			time.
+
+			See [IA32-v2a] "HLT", [IA32-v2b] "STI", and [IA32-v3a]
+			7.11.1 "HLT Instruction". */
+		asm volatile("sti; hlt" : : : "memory");
+	}
+}
+
+/* Function used as the basis for a kernel thread. */
+static void kernel_thread(thread_func* function, void* aux)
+{
+	ASSERT(function != NULL);
+
+	intr_enable(); /* The scheduler runs with interrupts off. */
+	function(aux); /* Execute the thread function. */
+	thread_exit(); /* If function() returns, kill the thread. */
+}
+
+/* Returns the running thread. */
+struct thread* running_thread(void)
+{
+	uint32_t* esp;
+
+	/* Copy the CPU's stack pointer into `esp', and then round that
+		down to the start of a page.  Because `struct thread' is
+		always at the beginning of a page and the stack pointer is
+		somewhere in the middle, this locates the curent thread. */
+	asm("mov %%esp, %0" : "=g"(esp));
+	return pg_round_down(esp);
+}
+
+/* Returns true if T appears to point to a valid thread. */
+static bool is_thread(struct thread* t)
+{
+	return t != NULL && t->magic == THREAD_MAGIC;
+}
+
+/* Does basic initialization of T as a blocked thread named
+	NAME. */
+static void init_thread(struct thread* t, const char* name, int priority)
+{
+	enum intr_level old_level;
+
+	ASSERT(t != NULL);
+	ASSERT(PRI_MIN <= priority && priority <= PRI_MAX);
+	ASSERT(name != NULL);
+
+	memset(t, 0, sizeof *t);
+	t->child_waits = false;
+	t->status = THREAD_BLOCKED;
+	strlcpy(t->name, name, sizeof t->name);
+	t->stack = (uint8_t*) t + PGSIZE;
+	t->priority = priority;
+	t->magic = THREAD_MAGIC;
+
+	old_level = intr_disable();
+	list_push_back(&all_list, &t->allelem);
+	list_init(&t->child_list); 
+	intr_set_level(old_level);
+}
+
+/* Allocates a SIZE-byte frame at the top of thread T's stack and
+	returns a pointer to the frame's base. */
+static void* alloc_frame(struct thread* t, size_t size)
+{
+	/* Stack data is always allocated in word-size units. */
+	ASSERT(is_thread(t));
+	ASSERT(size % sizeof(uint32_t) == 0);
+
+	t->stack -= size;
+	return t->stack;
+}
+
+/* Chooses and returns the next thread to be scheduled.  Should
+	return a thread from the run queue, unless the run queue is
+	empty.  (If the running thread can continue running, then it
+	will be in the run queue.)  If the run queue is empty, return
+	idle_thread. */
+static struct thread* next_thread_to_run(void)
+{
+	if (list_empty(&ready_list))
+		return idle_thread;
+	else
+		return list_entry(list_pop_front(&ready_list), struct thread, elem);
+}
+
+/* Completes a thread switch by activating the new thread's page
+	tables, and, if the previous thread is dying, destroying it.
+
+	At this function's invocation, we just switched from thread
+	PREV, the new thread is already running, and interrupts are
+	still disabled.  This function is normally invoked by
+	thread_schedule() as its final action before returning, but
+	the first time a thread is scheduled it is called by
+	switch_entry() (see switch.S).
+
+	It's not safe to call printf() until the thread switch is
+	complete.  In practice that means that printf()s should be
+	added at the end of the function.
+
+	After this function and its caller returns, the thread switch
+	is complete. */
+void thread_schedule_tail(struct thread* prev)
+{
+	struct thread* cur = running_thread();
+
+	ASSERT(intr_get_level() == INTR_OFF);
+
+	/* Mark us as running. */
+	cur->status = THREAD_RUNNING;
+
+	/* Start new time slice. */
+	thread_ticks = 0;
+
+#ifdef USERPROG
+	/* Activate the new address space. */
+	process_activate();
+#endif
+
+	/* If the thread we switched from is dying, destroy its struct
+		thread.  This must happen late so that thread_exit() doesn't
+		pull out the rug under itself.  (We don't free
+		initial_thread because its memory was not obtained via
+		palloc().) */
+	if (prev != NULL && prev->status == THREAD_DYING && prev != initial_thread) {
+		ASSERT(prev != cur);
+		palloc_free_page(prev);
+	}
+}
+
+/* Schedules a new process.  At entry, interrupts must be off and
+	the running process's state must have been changed from
+	running to some other state.  This function finds another
+	thread to run and switches to it.
+
+	It's not safe to call printf() until thread_schedule_tail()
+	has completed. */
+static void schedule(void)
+{
+	struct thread* cur = running_thread();
+	struct thread* next = next_thread_to_run();
+	struct thread* prev = NULL;
+
+	ASSERT(intr_get_level() == INTR_OFF);
+	ASSERT(cur->status != THREAD_RUNNING);
+	ASSERT(is_thread(next));
+
+	if (cur != next)
+		prev = switch_threads(cur, next);
+	thread_schedule_tail(prev);
+}
+
+/* Returns a tid to use for a new thread. */
+static tid_t allocate_tid(void)
+{
+	static tid_t next_tid = 1;
+	tid_t tid;
+
+	lock_acquire(&tid_lock);
+	tid = next_tid++;
+	lock_release(&tid_lock);
+
+	return tid;
+}
+
+/* Offset of `stack' member within `struct thread'.
+	Used by switch.S, which can't figure it out on its own. */
+uint32_t thread_stack_ofs = offsetof(struct thread, stack);
+
+bool DEBUG_thread_create_simulate_fail(void)
+{
+    if (thread_create_limit == 0)
+        return false;
+    else
+        return --thread_create_limit == 0;
+}
diff --git a/threads/thread.h b/threads/thread.h
new file mode 100644
index 0000000000000000000000000000000000000000..152539e068e598d01f69087b2626aa08088195fd
--- /dev/null
+++ b/threads/thread.h
@@ -0,0 +1,225 @@
+#ifndef THREADS_THREAD_H
+#define THREADS_THREAD_H
+
+#include <debug.h>
+#include <list.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "lib/kernel/list.h"
+#include "synch.h"
+//#include "userprog/process.h"
+
+
+
+#include "synch.h"
+
+/* States in a thread's life cycle. */
+enum thread_status {
+	THREAD_RUNNING, /* Running thread. */
+	THREAD_READY,	 /* Not running but ready to run. */
+	THREAD_BLOCKED, /* Waiting for an event to trigger. */
+	THREAD_DYING	 /* About to be destroyed. */
+};
+
+
+/* Thread identifier type.
+	You can redefine this to whatever type you like. */
+typedef int tid_t;
+#define TID_ERROR ((tid_t) -1) /* Error value for tid_t. */
+
+// struct tid_threads;
+/* 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. */
+
+#ifdef USERPROG
+
+struct file_descriptors{
+  char * file;
+  int fd;
+};
+
+struct  children{
+	int exit_status;
+	struct semaphore wait_for_child;
+//	struct lock _lock;
+	struct list_elem child_elem;
+	tid_t tid;	
+	//struct thread * parent;
+	bool check;
+	int alive_count;
+	bool has_been_called;
+	bool has_awaken;
+	struct lock lock_;
+
+};
+#endif
+
+
+
+
+/* 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. */
+#ifdef USERPROG
+	struct file_descriptors{
+   		char * file;
+   		int fd;
+		};
+#endif
+
+
+struct thread {
+	/* Owned by thread.c. */
+	tid_t tid;						/* Thread identifier. */
+	enum thread_status status; /* Thread state. */
+	char name[16];					/* Name (for debugging purposes). */
+	uint8_t* stack;				/* Saved stack pointer. */
+	int priority;					/* Priority. */
+	struct list_elem allelem;	/* List element for all threads list. */
+
+	
+
+	/* Shared between thread.c and synch.c. */
+	struct list_elem elem; /* List element. */
+	
+	//bool child_done;
+	//struct thread* parent; //maybe false?
+
+	
+
+#ifdef USERPROG
+	/* Owned by userprog/process.c. */
+
+	struct semaphore thread_sema;
+	 
+	struct file_descriptors file_descriptor[128];
+
+
+	
+	uint32_t* pagedir; /* Page directory. */
+	struct semaphore sema;
+	int exit_status;
+	struct file_descriptors file_descriptor[128];
+	//struct file_descriptors * open_files[128];
+
+	struct list child_list;
+	
+	struct children * current_child;
+
+	bool child_waits;
+
+//	struct semaphore wait_for_child;
+#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;
+
+///////
+extern int thread_create_limit;
+///////
+
+void thread_init(void);
+void thread_start(void);
+
+void thread_tick(void);
+void thread_print_stats(void);
+
+typedef void thread_func(void* aux);
+tid_t thread_create(const char* name, int priority, thread_func*, void*);
+
+void thread_block(void);
+void thread_unblock(struct thread*);
+
+struct thread* thread_current(void);
+tid_t thread_tid(void);
+const char* thread_name(void);
+
+void thread_exit(void) NO_RETURN;
+void thread_yield(void);
+
+/* Performs some operation on thread t, given auxiliary data AUX. */
+typedef void thread_action_func(struct thread* t, void* aux);
+void thread_foreach(thread_action_func*, void*);
+
+int thread_get_priority(void);
+void thread_set_priority(int);
+
+int thread_get_nice(void);
+void thread_set_nice(int);
+int thread_get_recent_cpu(void);
+int thread_get_load_avg(void);
+//////
+bool DEBUG_thread_create_simulate_fail(void);
+
+
+#endif /* threads/thread.h */
+
+
+
diff --git a/threads/vaddr.h b/threads/vaddr.h
new file mode 100644
index 0000000000000000000000000000000000000000..a9f8b707626411181f2aee9dddc5ef4597aca97d
--- /dev/null
+++ b/threads/vaddr.h
@@ -0,0 +1,89 @@
+#ifndef THREADS_VADDR_H
+#define THREADS_VADDR_H
+
+#include "threads/loader.h"
+
+#include <debug.h>
+#include <stdbool.h>
+#include <stdint.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/userprog/.gitignore b/userprog/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6d5357c015ab6f0b7ee7074381afdd3da82e06eb
--- /dev/null
+++ b/userprog/.gitignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/userprog/Make.vars b/userprog/Make.vars
new file mode 100644
index 0000000000000000000000000000000000000000..f8139f5ecf38ba09a5b408a190724ae233a0521a
--- /dev/null
+++ b/userprog/Make.vars
@@ -0,0 +1,10 @@
+# -*- makefile -*-
+
+kernel.bin: DEFINES = -DUSERPROG -DFILESYS
+KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
+TEST_SUBDIRS = tests/userprog tests/klaar tests/filst tests/filesys/base tests/dagjo
+#tests/userprog/no-vm will only pass if page faults in kernel space are caught
+#and treated as errors in userspace
+GRADING_FILE = $(SRCDIR)/tests/userprog/Grading
+SIMULATOR = --qemu
+
diff --git a/userprog/Makefile b/userprog/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..34c10aa4f508714da040e81389fa51e56ba2d97a
--- /dev/null
+++ b/userprog/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel
diff --git a/userprog/exception.c b/userprog/exception.c
new file mode 100644
index 0000000000000000000000000000000000000000..012d406557d7031a3baffe43cc266b9efd672e3c
--- /dev/null
+++ b/userprog/exception.c
@@ -0,0 +1,161 @@
+#include "userprog/exception.h"
+
+#include "threads/interrupt.h"
+#include "threads/thread.h"
+#include "userprog/gdt.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+/* Number of page faults processed. */
+static long long page_fault_cnt;
+
+static void kill(struct intr_frame*);
+static void page_fault(struct intr_frame*);
+
+/* Registers handlers for interrupts that can be caused by user
+	programs.
+
+	In a real Unix-like OS, most of these interrupts would be
+	passed along to the user process in the form of signals, as
+	described in [SV-386] 3-24 and 3-25, but we don't implement
+	signals.  Instead, we'll make them simply kill the user
+	process.
+
+	Page faults are an exception.  Here they are treated the same
+	way as other exceptions, but this will need to change to
+	implement virtual memory.
+
+	Refer to [IA32-v3a] section 5.15 "Exception and Interrupt
+	Reference" for a description of each of these exceptions. */
+void exception_init(void)
+{
+	/* These exceptions can be raised explicitly by a user program,
+		e.g. via the INT, INT3, INTO, and BOUND instructions.  Thus,
+		we set DPL==3, meaning that user programs are allowed to
+		invoke them via these instructions. */
+	intr_register_int(3, 3, INTR_ON, kill, "#BP Breakpoint Exception");
+	intr_register_int(4, 3, INTR_ON, kill, "#OF Overflow Exception");
+	intr_register_int(5, 3, INTR_ON, kill, "#BR BOUND Range Exceeded Exception");
+
+	/* These exceptions have DPL==0, preventing user processes from
+		invoking them via the INT instruction.  They can still be
+		caused indirectly, e.g. #DE can be caused by dividing by
+		0.  */
+	intr_register_int(0, 0, INTR_ON, kill, "#DE Divide Error");
+	intr_register_int(1, 0, INTR_ON, kill, "#DB Debug Exception");
+	intr_register_int(6, 0, INTR_ON, kill, "#UD Invalid Opcode Exception");
+	intr_register_int(7, 0, INTR_ON, kill, "#NM Device Not Available Exception");
+	intr_register_int(11, 0, INTR_ON, kill, "#NP Segment Not Present");
+	intr_register_int(12, 0, INTR_ON, kill, "#SS Stack Fault Exception");
+	intr_register_int(13, 0, INTR_ON, kill, "#GP General Protection Exception");
+	intr_register_int(16, 0, INTR_ON, kill, "#MF x87 FPU Floating-Point Error");
+	intr_register_int(19, 0, INTR_ON, kill, "#XF SIMD Floating-Point Exception");
+
+	/* Most exceptions can be handled with interrupts turned on.
+		We need to disable interrupts for page faults because the
+		fault address is stored in CR2 and needs to be preserved. */
+	intr_register_int(14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception");
+}
+
+/* Prints exception statistics. */
+void exception_print_stats(void)
+{
+	printf("Exception: %lld page faults\n", page_fault_cnt);
+}
+
+/* Handler for an exception (probably) caused by a user process. */
+static void kill(struct intr_frame* f)
+{
+	/* This interrupt is one (probably) caused by a user process.
+		For example, the process might have tried to access unmapped
+		virtual memory (a page fault).  For now, we simply kill the
+		user process.  Later, we'll want to handle page faults in
+		the kernel.  Real Unix-like operating systems pass most
+		exceptions back to the process via signals, but we don't
+		implement them. */
+
+	/* The interrupt frame's code segment value tells us where the
+		exception originated. */
+	switch (f->cs) {
+		case SEL_UCSEG:
+			/* User's code segment, so it's a user exception, as we
+				expected.  Kill the user process.  */
+			printf(
+				 "%s: dying due to interrupt %#04x (%s).\n",
+				 thread_name(),
+				 f->vec_no,
+				 intr_name(f->vec_no));
+			intr_dump_frame(f);
+			thread_exit();
+
+		case SEL_KCSEG:
+			/* Kernel's code segment, which indicates a kernel bug.
+				Kernel code shouldn't throw exceptions.  (Page faults
+				may cause kernel exceptions--but they shouldn't arrive
+				here.)  Panic the kernel to make the point.  */
+			intr_dump_frame(f);
+			PANIC("Kernel bug - unexpected interrupt in kernel");
+
+		default:
+			/* Some other code segment?  Shouldn't happen.  Panic the
+				kernel. */
+			printf(
+				 "Interrupt %#04x (%s) in unknown segment %04x\n",
+				 f->vec_no,
+				 intr_name(f->vec_no),
+				 f->cs);
+			thread_exit();
+	}
+}
+
+/* Page fault handler.  This is a skeleton that must be filled in
+	to implement virtual memory.  Some solutions to project 2 may
+	also require modifying this code.
+
+	At entry, the address that faulted is in CR2 (Control Register
+	2) and information about the fault, formatted as described in
+	the PF_* macros in exception.h, is in F's error_code member.  The
+	example code here shows how to parse that information.  You
+	can find more information about both of these in the
+	description of "Interrupt 14--Page Fault Exception (#PF)" in
+	[IA32-v3a] section 5.15 "Exception and Interrupt Reference". */
+static void page_fault(struct intr_frame* f)
+{
+	bool not_present; /* True: not-present page, false: writing r/o page. */
+	bool write;			/* True: access was write, false: access was read. */
+	bool user;			/* True: access by user, false: access by kernel. */
+	void* fault_addr; /* Fault address. */
+
+	/* Obtain faulting address, the virtual address that was
+		accessed to cause the fault.  It may point to code or to
+		data.  It is not necessarily the address of the instruction
+		that caused the fault (that's f->eip).
+		See [IA32-v2a] "MOV--Move to/from Control Registers" and
+		[IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception
+		(#PF)". */
+	asm("movl %%cr2, %0" : "=r"(fault_addr));
+
+	/* Turn interrupts back on (they were only off so that we could
+		be assured of reading CR2 before it changed). */
+	intr_enable();
+
+	/* Count page faults. */
+	page_fault_cnt++;
+
+	/* Determine cause. */
+	not_present = (f->error_code & PF_P) == 0;
+	write = (f->error_code & PF_W) != 0;
+	user = (f->error_code & PF_U) != 0;
+
+	/* To implement virtual memory, 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/userprog/exception.h b/userprog/exception.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d833ab3fff1a2f0e584bded4b0596158d1c3651
--- /dev/null
+++ b/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/userprog/gdt.c b/userprog/gdt.c
new file mode 100644
index 0000000000000000000000000000000000000000..aa131d38c5753c00d72b833962f21419abbfa5c9
--- /dev/null
+++ b/userprog/gdt.c
@@ -0,0 +1,142 @@
+#include "userprog/gdt.h"
+
+#include "threads/palloc.h"
+#include "threads/vaddr.h"
+#include "userprog/tss.h"
+
+#include <debug.h>
+
+/* The Global Descriptor Table (GDT).
+
+	The GDT, an x86-specific structure, defines segments that can
+	potentially be used by all processes in a system, subject to
+	their permissions.  There is also a per-process Local
+	Descriptor Table (LDT) but that is not used by modern
+	operating systems.
+
+	Each entry in the GDT, which is known by its byte offset in
+	the table, identifies a segment.  For our purposes only three
+	types of segments are of interest: code, data, and TSS or
+	Task-State Segment descriptors.  The former two types are
+	exactly what they sound like.  The TSS is used primarily for
+	stack switching on interrupts.
+
+	For more information on the GDT as used here, refer to
+	[IA32-v3a] 3.2 "Using Segments" through 3.5 "System Descriptor
+	Types". */
+static uint64_t gdt[SEL_CNT];
+
+/* GDT helpers. */
+static uint64_t make_code_desc(int dpl);
+static uint64_t make_data_desc(int dpl);
+static uint64_t make_tss_desc(void* laddr);
+static uint64_t make_gdtr_operand(uint16_t limit, void* base);
+
+/* Sets up a proper GDT.  The bootstrap loader's GDT didn't
+	include user-mode selectors or a TSS, but we need both now. */
+void gdt_init(void)
+{
+	uint64_t gdtr_operand;
+
+	/* Initialize GDT. */
+	gdt[SEL_NULL / sizeof *gdt] = 0;
+	gdt[SEL_KCSEG / sizeof *gdt] = make_code_desc(0);
+	gdt[SEL_KDSEG / sizeof *gdt] = make_data_desc(0);
+	gdt[SEL_UCSEG / sizeof *gdt] = make_code_desc(3);
+	gdt[SEL_UDSEG / sizeof *gdt] = make_data_desc(3);
+	gdt[SEL_TSS / sizeof *gdt] = make_tss_desc(tss_get());
+
+	/* Load GDTR, TR.  See [IA32-v3a] 2.4.1 "Global Descriptor
+		Table Register (GDTR)", 2.4.4 "Task Register (TR)", and
+		6.2.4 "Task Register".  */
+	gdtr_operand = make_gdtr_operand(sizeof gdt - 1, gdt);
+	asm volatile("lgdt %0" : : "m"(gdtr_operand));
+	asm volatile("ltr %w0" : : "q"(SEL_TSS));
+}
+
+/* System segment or code/data segment? */
+enum seg_class {
+	CLS_SYSTEM = 0,	/* System segment. */
+	CLS_CODE_DATA = 1 /* Code or data segment. */
+};
+
+/* Limit has byte or 4 kB page granularity? */
+enum seg_granularity {
+	GRAN_BYTE = 0, /* Limit has 1-byte granularity. */
+	GRAN_PAGE = 1	/* Limit has 4 kB granularity. */
+};
+
+/* Returns a segment descriptor with the given 32-bit BASE and
+	20-bit LIMIT (whose interpretation depends on GRANULARITY).
+	The descriptor represents a system or code/data segment
+	according to CLASS, and TYPE is its type (whose interpretation
+	depends on the class).
+
+	The segment has descriptor privilege level DPL, meaning that
+	it can be used in rings numbered DPL or lower.  In practice,
+	DPL==3 means that user processes can use the segment and
+	DPL==0 means that only the kernel can use the segment.  See
+	[IA32-v3a] 4.5 "Privilege Levels" for further discussion. */
+static uint64_t make_seg_desc(
+	 uint32_t base,
+	 uint32_t limit,
+	 enum seg_class class,
+	 int type,
+	 int dpl,
+	 enum seg_granularity granularity)
+{
+	uint32_t e0, e1;
+
+	ASSERT(limit <= 0xfffff);
+	ASSERT(class == CLS_SYSTEM || class == CLS_CODE_DATA);
+	ASSERT(type >= 0 && type <= 15);
+	ASSERT(dpl >= 0 && dpl <= 3);
+	ASSERT(granularity == GRAN_BYTE || granularity == GRAN_PAGE);
+
+	e0
+		 = ((limit & 0xffff) /* Limit 15:0. */
+			 | (base << 16)); /* Base 15:0. */
+
+	e1
+		 = (((base >> 16) & 0xff)	 /* Base 23:16. */
+			 | (type << 8)				 /* Segment type. */
+			 | (class << 12)			 /* 0=system, 1=code/data. */
+			 | (dpl << 13)				 /* Descriptor privilege. */
+			 | (1 << 15)				 /* Present. */
+			 | (limit & 0xf0000)		 /* Limit 16:19. */
+			 | (1 << 22)				 /* 32-bit segment. */
+			 | (granularity << 23)	 /* Byte/page granularity. */
+			 | (base & 0xff000000)); /* Base 31:24. */
+
+	return e0 | ((uint64_t) e1 << 32);
+}
+
+/* Returns a descriptor for a readable code segment with base at
+	0, a limit of 4 GB, and the given DPL. */
+static uint64_t make_code_desc(int dpl)
+{
+	return make_seg_desc(0, 0xfffff, CLS_CODE_DATA, 10, dpl, GRAN_PAGE);
+}
+
+/* Returns a descriptor for a writable data segment with base at
+	0, a limit of 4 GB, and the given DPL. */
+static uint64_t make_data_desc(int dpl)
+{
+	return make_seg_desc(0, 0xfffff, CLS_CODE_DATA, 2, dpl, GRAN_PAGE);
+}
+
+/* Returns a descriptor for an "available" 32-bit Task-State
+	Segment with its base at the given linear address, a limit of
+	0x67 bytes (the size of a 32-bit TSS), and a DPL of 0.
+	See [IA32-v3a] 6.2.2 "TSS Descriptor". */
+static uint64_t make_tss_desc(void* laddr)
+{
+	return make_seg_desc((uint32_t) laddr, 0x67, CLS_SYSTEM, 9, 0, GRAN_BYTE);
+}
+
+/* Returns a descriptor that yields the given LIMIT and BASE when
+	used as an operand for the LGDT instruction. */
+static uint64_t make_gdtr_operand(uint16_t limit, void* base)
+{
+	return limit | ((uint64_t) (uint32_t) base << 16);
+}
diff --git a/userprog/gdt.h b/userprog/gdt.h
new file mode 100644
index 0000000000000000000000000000000000000000..b621914e93ef9b61b12976df9c569d793b75f84a
--- /dev/null
+++ b/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/userprog/pagedir.c b/userprog/pagedir.c
new file mode 100644
index 0000000000000000000000000000000000000000..78ead8027755fd93d3b902d57094fa14a0a931d8
--- /dev/null
+++ b/userprog/pagedir.c
@@ -0,0 +1,242 @@
+#include "userprog/pagedir.h"
+
+#include "threads/init.h"
+#include "threads/palloc.h"
+#include "threads/pte.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+
+static uint32_t* active_pd(void);
+static void invalidate_pagedir(uint32_t*);
+
+/* Creates a new page directory that has mappings for kernel
+	virtual addresses, but none for user virtual addresses.
+	Returns the new page directory, or a null pointer if memory
+	allocation fails. */
+uint32_t* pagedir_create(void)
+{
+	uint32_t* pd = palloc_get_page(0);
+	if (pd != NULL)
+		memcpy(pd, init_page_dir, PGSIZE);
+	return pd;
+}
+
+/* Destroys page directory PD, freeing all the pages it
+	references. */
+void pagedir_destroy(uint32_t* pd)
+{
+	uint32_t* pde;
+
+	if (pd == NULL)
+		return;
+
+	ASSERT(pd != init_page_dir);
+	for (pde = pd; pde < pd + pd_no(PHYS_BASE); pde++)
+		if (*pde & PTE_P) {
+			uint32_t* pt = pde_get_pt(*pde);
+			uint32_t* pte;
+
+			for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++)
+				if (*pte & PTE_P)
+					palloc_free_page(pte_get_page(*pte));
+			palloc_free_page(pt);
+		}
+	palloc_free_page(pd);
+}
+
+/* Returns the address of the page table entry for virtual
+	address VADDR in page directory PD.
+	If PD does not have a page table for VADDR, behavior depends
+	on CREATE.  If CREATE is true, then a new page table is
+	created and a pointer into it is returned.  Otherwise, a null
+	pointer is returned. */
+static uint32_t* lookup_page(uint32_t* pd, const void* vaddr, bool create)
+{
+	uint32_t *pt, *pde;
+
+	ASSERT(pd != NULL);
+
+	/* Shouldn't create new kernel virtual mappings. */
+	ASSERT(!create || is_user_vaddr(vaddr));
+
+	/* Check for a page table for VADDR.
+		If one is missing, create one if requested. */
+	pde = pd + pd_no(vaddr);
+	if (*pde == 0) {
+		if (create) {
+			pt = palloc_get_page(PAL_ZERO);
+			if (pt == NULL)
+				return NULL;
+
+			*pde = pde_create(pt);
+		}
+		else
+			return NULL;
+	}
+
+	/* Return the page table entry. */
+	pt = pde_get_pt(*pde);
+	return &pt[pt_no(vaddr)];
+}
+
+/* Adds a mapping in page directory PD from user virtual page
+	UPAGE to the physical frame identified by kernel virtual
+	address KPAGE.
+	UPAGE must not already be mapped.
+	KPAGE should probably be a page obtained from the user pool
+	with palloc_get_page().
+	If WRITABLE is true, the new page is read/write;
+	otherwise it is read-only.
+	Returns true if successful, false if memory allocation
+	failed. */
+bool pagedir_set_page(uint32_t* pd, void* upage, void* kpage, bool writable)
+{
+	uint32_t* pte;
+
+	ASSERT(pg_ofs(upage) == 0);
+	ASSERT(pg_ofs(kpage) == 0);
+	ASSERT(is_user_vaddr(upage));
+	ASSERT(vtop(kpage) >> PTSHIFT < init_ram_pages);
+	ASSERT(pd != init_page_dir);
+
+	pte = lookup_page(pd, upage, true);
+
+	if (pte != NULL) {
+		ASSERT((*pte & PTE_P) == 0);
+		*pte = pte_create_user(kpage, writable);
+		return true;
+	}
+	else
+		return false;
+}
+
+/* Looks up the physical address that corresponds to user virtual
+	address UADDR in PD.  Returns the kernel virtual address
+	corresponding to that physical address, or a null pointer if
+	UADDR is unmapped. */
+void* pagedir_get_page(uint32_t* pd, const void* uaddr)
+{
+	uint32_t* pte;
+
+	ASSERT(is_user_vaddr(uaddr));
+
+	pte = lookup_page(pd, uaddr, false);
+	if (pte != NULL && (*pte & PTE_P) != 0)
+		return pte_get_page(*pte) + pg_ofs(uaddr);
+	else
+		return NULL;
+}
+
+/* Marks user virtual page UPAGE "not present" in page
+	directory PD.  Later accesses to the page will fault.  Other
+	bits in the page table entry are preserved.
+	UPAGE need not be mapped. */
+void pagedir_clear_page(uint32_t* pd, void* upage)
+{
+	uint32_t* pte;
+
+	ASSERT(pg_ofs(upage) == 0);
+	ASSERT(is_user_vaddr(upage));
+
+	pte = lookup_page(pd, upage, false);
+	if (pte != NULL && (*pte & PTE_P) != 0) {
+		*pte &= ~PTE_P;
+		invalidate_pagedir(pd);
+	}
+}
+
+/* Returns true if the PTE for virtual page VPAGE in PD is dirty,
+	that is, if the page has been modified since the PTE was
+	installed.
+	Returns false if PD contains no PTE for VPAGE. */
+bool pagedir_is_dirty(uint32_t* pd, const void* vpage)
+{
+	uint32_t* pte = lookup_page(pd, vpage, false);
+	return pte != NULL && (*pte & PTE_D) != 0;
+}
+
+/* Set the dirty bit to DIRTY in the PTE for virtual page VPAGE
+	in PD. */
+void pagedir_set_dirty(uint32_t* pd, const void* vpage, bool dirty)
+{
+	uint32_t* pte = lookup_page(pd, vpage, false);
+	if (pte != NULL) {
+		if (dirty)
+			*pte |= PTE_D;
+		else {
+			*pte &= ~(uint32_t) PTE_D;
+			invalidate_pagedir(pd);
+		}
+	}
+}
+
+/* Returns true if the PTE for virtual page VPAGE in PD has been
+	accessed recently, that is, between the time the PTE was
+	installed and the last time it was cleared.  Returns false if
+	PD contains no PTE for VPAGE. */
+bool pagedir_is_accessed(uint32_t* pd, const void* vpage)
+{
+	uint32_t* pte = lookup_page(pd, vpage, false);
+	return pte != NULL && (*pte & PTE_A) != 0;
+}
+
+/* Sets the accessed bit to ACCESSED in the PTE for virtual page
+	VPAGE in PD. */
+void pagedir_set_accessed(uint32_t* pd, const void* vpage, bool accessed)
+{
+	uint32_t* pte = lookup_page(pd, vpage, false);
+	if (pte != NULL) {
+		if (accessed)
+			*pte |= PTE_A;
+		else {
+			*pte &= ~(uint32_t) PTE_A;
+			invalidate_pagedir(pd);
+		}
+	}
+}
+
+/* Loads page directory PD into the CPU's page directory base
+	register. */
+void pagedir_activate(uint32_t* pd)
+{
+	if (pd == NULL)
+		pd = init_page_dir;
+
+	/* Store the physical address of the page directory into CR3
+		aka PDBR (page directory base register).  This activates our
+		new page tables immediately.  See [IA32-v2a] "MOV--Move
+		to/from Control Registers" and [IA32-v3a] 3.7.5 "Base
+		Address of the Page Directory". */
+	asm volatile("movl %0, %%cr3" : : "r"(vtop(pd)) : "memory");
+}
+
+/* Returns the currently active page directory. */
+static uint32_t* active_pd(void)
+{
+	/* Copy CR3, the page directory base register (PDBR), into
+		`pd'.
+		See [IA32-v2a] "MOV--Move to/from Control Registers" and
+		[IA32-v3a] 3.7.5 "Base Address of the Page Directory". */
+	uintptr_t pd;
+	asm volatile("movl %%cr3, %0" : "=r"(pd));
+	return ptov(pd);
+}
+
+/* Seom page table changes can cause the CPU's translation
+	lookaside buffer (TLB) to become out-of-sync with the page
+	table.  When this happens, we have to "invalidate" the TLB by
+	re-activating it.
+
+	This function invalidates the TLB if PD is the active page
+	directory.  (If PD is not active then its entries are not in
+	the TLB, so there is no need to invalidate anything.) */
+static void invalidate_pagedir(uint32_t* pd)
+{
+	if (active_pd() == pd) {
+		/* Re-activating PD clears the TLB.  See [IA32-v3a] 3.12
+			"Translation Lookaside Buffers (TLBs)". */
+		pagedir_activate(pd);
+	}
+}
diff --git a/userprog/pagedir.h b/userprog/pagedir.h
new file mode 100644
index 0000000000000000000000000000000000000000..f350485fc56fd0cc83b24023353763c55668ce3c
--- /dev/null
+++ b/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/userprog/process.c b/userprog/process.c
new file mode 100644
index 0000000000000000000000000000000000000000..ef5c1e93163249203b026bd640fd7c203af59797
--- /dev/null
+++ b/userprog/process.c
@@ -0,0 +1,754 @@
+#include "userprog/process.h"
+#include "filesys/directory.h"
+#include "filesys/file.h"
+#include "filesys/filesys.h"
+#include "threads/flags.h"
+#include "threads/init.h"
+#include "threads/interrupt.h"
+#include "threads/palloc.h"
+#include "threads/vaddr.h"
+#include "userprog/gdt.h"
+#include "userprog/pagedir.h"
+#include "userprog/tss.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+#include <debug.h>
+#include <inttypes.h>
+#include <round.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "lib/kernel/list.h"
+
+
+static thread_func start_process NO_RETURN;
+static bool load(const char* file_name, void (**eip)(void), void** esp);
+static void dump_stack(const void* esp);
+
+struct tid_threads{
+	struct semaphore sema;
+	const char* cmd;
+	struct children*child;
+};
+
+/* Starts a new thread running a user program loaded from
+	CMD_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. */
+
+tid_t process_execute(const char* cmd_line)
+{
+	char* cl_copy;
+	tid_t tid;
+	if (cmd_line == NULL){
+		return TID_ERROR;
+	}
+
+	cl_copy = palloc_get_page(0);
+	if (cl_copy == NULL){return TID_ERROR;}
+	
+	strlcpy(cl_copy, cmd_line, PGSIZE);
+
+	/////////////////lab4/////////////
+	struct tid_threads* thread_child =malloc(sizeof(struct tid_threads));
+	thread_child->cmd = cl_copy;
+	sema_init(&(thread_child->sema),0);
+
+
+	///////////////////////////////////////////////////////////////////////
+	struct children * child_ = malloc(sizeof(struct children));
+
+	child_->alive_count=2; 
+
+	sema_init(&child_->wait_for_child,0);
+
+	thread_child->child = child_;
+
+
+
+
+	char *token_ = palloc_get_page(0);
+	if (token_ == NULL)
+		return -1;
+	strlcpy(token_, cl_copy, PGSIZE);
+	char *save_ptr;
+	char * token_save = strtok_r (token_, " ", &save_ptr);	
+	struct thread *parent =thread_current();
+
+	
+	/* Create a new thread to execute FILE_NAME. */
+	tid = thread_create(token_save, PRI_DEFAULT, start_process, thread_child);
+
+	if (tid == TID_ERROR ){
+		palloc_free_page(cl_copy);
+		palloc_free_page(token_);
+		free(child_); 
+		free(thread_child);
+		return tid;
+	}
+	
+	
+
+	/////////////////lab5/////////////
+	
+	sema_down(&thread_child->sema);
+	child_->tid = tid;
+	if (child_->check ){
+
+		enum intr_level old_level;
+		old_level = intr_disable(); //interrupt is disabled to protect the list
+
+		list_push_back(&parent->child_list,&child_->child_elem);
+		intr_set_level(old_level);
+
+	}
+	else{
+		free(child_);
+		free(thread_child);
+		return TID_ERROR;
+
+	}
+	
+	
+	return tid;
+}
+
+
+
+
+///////////////////////////////// lab1 ///////////////////////////////////
+/* make_stack modifies and fixes esp*/
+void make_stack(char *argv[],int argc, void **esp){
+
+		/* put the arguments into the stack*/
+		int i = (int)*esp; 
+		for (int index = argc - 1; index > -1 ;index--){
+			
+			*esp = *esp-1-strlen(argv[index]);
+			memcpy(*esp, argv[index], strlen(argv[index])+1);
+		
+          }
+
+/* make sure that the stack is divisible by 4*/
+    if ((unsigned)(*esp)%4 != 0){
+		*esp -= (unsigned)(*esp)%4;
+    }
+	
+
+/* put a null to the stack*/
+	*esp = *esp-sizeof(char*);
+	 memcpy(*esp , "\0", 1);
+	 
+/* put the address of the arguments into the stack*/
+	char* pointer;
+    for (int j=argc-1; j > -1; j--){
+
+		i = i -1- strlen(argv[j]);
+		pointer = (char* )i;
+
+        *esp = *esp-sizeof(char*);
+		memcpy(*esp, &pointer, sizeof(char*));
+    }
+
+/* put the first adress of the argv into the stack*/
+    memcpy(*esp - sizeof(char**), &(*esp), sizeof(char**));
+	*esp = *esp - sizeof(char**);
+
+/* put the argc into the stack*/
+	*esp=*esp - sizeof(int);
+	memcpy(*esp , &argc, sizeof(int));
+
+/* return to the begining of the argv */
+	void * p; 
+	*esp=*esp - sizeof(void(*) ());
+	memcpy(*esp , &p, sizeof(void(*) ()));
+
+}
+//////////////////////////////////////////////////////////////////////////
+
+/* A thread function that loads a user process and starts it
+	running. */
+static void start_process(void* cmd_line_)
+{
+	struct tid_threads *args = (struct tid_threads*) cmd_line_;
+	char* cmd_line = args->cmd;
+
+	//struct children *child_= (struct children *) cmd_line_;
+	thread_current()->current_child = args->child;
+
+	struct intr_frame if_;
+	bool success;
+
+	/* 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;
+
+///////////////////////////////lab1///////////////////////////////////
+    char * save_ptr, * token;
+	char * argv[32];
+	int argc = 0;
+
+    for (token = strtok_r (cmd_line, " ", &save_ptr); token != NULL;
+          token = strtok_r (NULL, " ", &save_ptr)){
+			
+			argv[argc] = token;
+			if(argc >= 32){
+				break;
+			}
+            argc++;
+          }	
+
+////////////////////////////////////////////////////////////////////////
+
+	// Note: load requires the file name only, not the entire cmd_line
+	success = load(cmd_line, &if_.eip, &if_.esp);
+
+
+	thread_current()->current_child = args->child;
+	/* If load failed, quit. */
+	if (!success){
+		
+		args->child->exit_status = -1;
+		args->child->check = false;
+		sema_up(&args->sema);
+		thread_exit();
+		
+	}else{
+		args->child->check = true;
+	}
+
+	make_stack(argv,argc, &if_.esp);
+
+    sema_up(&args->sema);
+
+		
+//////////////////////// lab1 ///////////////////////
+
+
+	palloc_free_page(cmd_line);
+/////////////////////////////////////////////////////
+
+	/* Start the user process by simulating a return from an
+		interrupt, implemented by intr_exit (in
+		threads/intr-stubs.S).  Because intr_exit takes all of its
+		arguments on the stack in the form of a `struct intr_frame',
+		we just point the stack pointer (%esp) to our stack frame
+		and jump to it. */
+	asm volatile("movl %0, %%esp; jmp intr_exit" : : "g"(&if_) : "memory");
+
+
+	NOT_REACHED();
+}
+
+/* Waits for thread TID to die and returns its exit status.  If
+	it was terminated by the kernel (i.e. killed due to an
+	exception), returns -1.  If TID is invalid or if it was not a
+	child of the calling process, or if process_wait() has already
+	been successfully called for the given TID, returns -1
+	immediately, without waiting.
+
+	This function will be implemented in problem 2-2.  For now, it
+	does nothing. */
+int process_wait(tid_t child_tid ) 
+{
+	
+	
+	int exit_status= -1;
+	
+	struct thread * parent = thread_current();
+	if (parent->child_waits){
+		return exit_status;
+	}
+	if (!list_empty(&parent->child_list)){
+		struct list_elem *child_e;
+		for (child_e = list_begin(&parent->child_list); 
+				child_e != list_end(&parent->child_list); 
+					child_e = list_next(child_e))
+				{
+					struct children *child = list_entry(child_e, struct children, child_elem);
+			
+
+					if(child->tid != NULL && child->tid == child_tid ) {
+						
+						list_remove(child_e);
+
+						if ( child->alive_count == 2 ){
+
+							parent->child_waits = true;
+							
+							sema_down(&child->wait_for_child);
+							parent->child_waits = false;
+
+							
+						}
+						exit_status = child->exit_status;
+						child->exit_status=-1;
+						if (child->alive_count == 0){
+							free(child);
+						}
+						return exit_status;
+					
+					}	
+					
+				}
+	}
+	
+	return exit_status;
+	
+}
+
+/* Free the current process's resources. */
+void process_exit(void)
+{
+	struct thread* cur = thread_current();
+	uint32_t* pd;
+	/////////////lab5////////////
+	 struct thread *th = thread_current();
+
+
+	if(th->current_child != NULL){
+	
+		th->current_child->alive_count--;
+		if (th->current_child->alive_count ==0){
+			
+			free(th->current_child);
+		
+		}else {
+
+			sema_up(&th->current_child->wait_for_child); 
+		}
+		
+  	}
+
+	 if (!list_empty(&th->child_list)){
+
+		struct list_elem *child_e;
+		for (child_e = list_begin(&th->child_list); 
+				child_e != list_end(&th->child_list); 
+					child_e = list_next(child_e))
+				{
+					struct children *child = list_entry(child_e, struct children, child_elem);
+	
+						child->alive_count--;
+						if (child->alive_count==0){	
+							list_remove(child_e);
+							free(child);
+						}
+					
+				}
+		}
+
+
+
+
+
+
+	/* Destroy the current process's page directory and switch back
+		to the kernel-only page directory. */
+	pd = cur->pagedir;
+	if (pd != NULL) {
+		/* Correct ordering here is crucial.  We must set
+			cur->pagedir to NULL before switching page directories,
+			so that a timer interrupt can't switch back to the
+			process page directory.  We must activate the base page
+			directory before destroying the process's page
+			directory, or our active page directory will be one
+			that's been freed (and cleared). */
+		cur->pagedir = NULL;
+		pagedir_activate(NULL);
+		pagedir_destroy(pd);
+	}
+}
+
+/* Sets up the CPU for running user code in the current
+	thread.
+	This function is called on every context switch. */
+void process_activate(void)
+{
+	struct thread* t = thread_current();
+
+	/* Activate thread's page tables. */
+	pagedir_activate(t->pagedir);
+
+	/* Set thread's kernel stack for use in processing
+		interrupts. */
+	tss_update();
+}
+
+/* We load ELF binaries.  The following definitions are taken
+	from the ELF specification, [ELF1], more-or-less verbatim.  */
+
+/* ELF types.  See [ELF1] 1-2. */
+typedef uint32_t Elf32_Word, Elf32_Addr, Elf32_Off;
+typedef uint16_t Elf32_Half;
+
+/* For use with ELF types in printf(). */
+#define PE32Wx PRIx32 /* Print Elf32_Word in hexadecimal. */
+#define PE32Ax PRIx32 /* Print Elf32_Addr in hexadecimal. */
+#define PE32Ox PRIx32 /* Print Elf32_Off in hexadecimal. */
+#define PE32Hx PRIx16 /* Print Elf32_Half in hexadecimal. */
+
+/* Executable header.  See [ELF1] 1-4 to 1-8.
+	This appears at the very beginning of an ELF binary. */
+struct Elf32_Ehdr {
+	unsigned char e_ident[16];
+	Elf32_Half e_type;
+	Elf32_Half e_machine;
+	Elf32_Word e_version;
+	Elf32_Addr e_entry;
+	Elf32_Off e_phoff;
+	Elf32_Off e_shoff;
+	Elf32_Word e_flags;
+	Elf32_Half e_ehsize;
+	Elf32_Half e_phentsize;
+	Elf32_Half e_phnum;
+	Elf32_Half e_shentsize;
+	Elf32_Half e_shnum;
+	Elf32_Half e_shstrndx;
+};
+
+/* Program header.  See [ELF1] 2-2 to 2-4.
+	There are e_phnum of these, starting at file offset e_phoff
+	(see [ELF1] 1-6). */
+struct Elf32_Phdr {
+	Elf32_Word p_type;
+	Elf32_Off p_offset;
+	Elf32_Addr p_vaddr;
+	Elf32_Addr p_paddr;
+	Elf32_Word p_filesz;
+	Elf32_Word p_memsz;
+	Elf32_Word p_flags;
+	Elf32_Word p_align;
+};
+
+/* Values for p_type.  See [ELF1] 2-3. */
+#define PT_NULL	 0				/* Ignore. */
+#define PT_LOAD	 1				/* Loadable segment. */
+#define PT_DYNAMIC 2				/* Dynamic linking info. */
+#define PT_INTERP	 3				/* Name of dynamic loader. */
+#define PT_NOTE	 4				/* Auxiliary info. */
+#define PT_SHLIB	 5				/* Reserved. */
+#define PT_PHDR	 6				/* Program header table. */
+#define PT_STACK	 0x6474e551 /* Stack segment. */
+
+/* Flags for p_flags.  See [ELF3] 2-3 and 2-4. */
+#define PF_X 1 /* Executable. */
+#define PF_W 2 /* Writable. */
+#define PF_R 4 /* Readable. */
+
+static bool 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();
+
+
+
+
+	/* 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;
+		}
+	}
+
+	/* Set up stack. */
+	if (!setup_stack(esp))
+		goto done;
+
+
+
+
+	/* Start address. */
+	*eip = (void (*)(void)) ehdr.e_entry;
+
+	success = true;
+
+done:
+	/* We arrive here whether the load is successful or not. */
+	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 ; //-12
+		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));
+}
+
+// Don't raise a warning about unused function.
+// We know that dump_stack might not be called, this is fine.
+
+#pragma GCC diagnostic ignored "-Wunused-function"
+/* With the given stack pointer, will try and output the stack to STDOUT. */
+static void dump_stack(const void* esp)
+{
+	printf("*esp is %p\nstack contents:\n", esp);
+	hex_dump((int) esp, esp, PHYS_BASE - esp + 16, true);
+	/* The same information, only more verbose: */
+	/* It prints every byte as if it was a char and every 32-bit aligned
+		data as if it was a pointer. */
+	void* ptr_save = PHYS_BASE;
+	int i = -15;
+	while (ptr_save - i >= esp) {
+		char* whats_there = (char*) (ptr_save - i);
+		// show the address ...
+		printf("%x\t", (uint32_t) whats_there);
+		// ... printable byte content ...
+		if (*whats_there >= 32 && *whats_there < 127)
+			printf("%c\t", *whats_there);
+		else
+			printf(" \t");
+		// ... and 32-bit aligned content
+		if (i % 4 == 0) {
+			uint32_t* wt_uint32 = (uint32_t*) (ptr_save - i);
+			printf("%x\t", *wt_uint32);
+			printf("\n-------");
+			if (i != 0)
+				printf("------------------------------------------------");
+			else
+				printf(" the border between KERNEL SPACE and USER SPACE ");
+			printf("-------");
+		}
+		printf("\n");
+		i++;
+	}
+}
+#pragma GCC diagnostic pop
+
diff --git a/userprog/process.h b/userprog/process.h
new file mode 100644
index 0000000000000000000000000000000000000000..385386359d0df833257a4ab5badcc5136a4e57cf
--- /dev/null
+++ b/userprog/process.h
@@ -0,0 +1,42 @@
+#ifndef USERPROG_PROCESS_H
+#define USERPROG_PROCESS_H
+
+#include "threads/thread.h"
+#include "threads/synch.h"
+#include <stdbool.h>
+#include "lib/kernel/list.h"
+
+
+////////////////////lab4////////////////////////
+struct child {
+	
+	const char* child_name;
+	struct semaphore child_sema;
+	struct list_elem child_elem;
+	int exit_status;
+	tid_t  child_tid;
+};
+
+
+////////////////////////////////////////////////
+
+tid_t process_execute(const char* cmd_line);
+int process_wait(tid_t);
+void process_exit(void);
+void process_activate(void);
+
+
+
+// struct tid_threads{
+// 	struct thread * thread;
+// 	tid_t tid;
+// 	struct list_elem elem;
+// 	char* cmd;
+// 	struct list *  child_list;
+// 	int type;
+
+// };
+
+// void tid_threads_init(struct tid_threads * tt );
+
+#endif /* userprog/process.h */
diff --git a/userprog/slowdown.c b/userprog/slowdown.c
new file mode 100644
index 0000000000000000000000000000000000000000..810e19a80c57a7357cf188ba623ae4da2d695d20
--- /dev/null
+++ b/userprog/slowdown.c
@@ -0,0 +1,76 @@
+#include "userprog/slowdown.h"
+
+#include "lib/debug.h"
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+#include "threads/thread.h"
+
+/* Enabled? */
+static bool slowdown_active = false;
+
+/* Interrupt function called on every instruction. */
+static void trap_interrupt(struct intr_frame* args UNUSED)
+{
+	/* Just calling the trap is slow enough. We could spin on a call to barrier () here
+	 * if we need slower execution. */
+}
+
+/* Old syscall handler. */
+intr_handler_func* syscall_handler;
+
+/* Intercept the syscall handler. */
+static void trap_syscall(struct intr_frame* args)
+{
+	slowdown_enable();
+	syscall_handler(args);
+	slowdown_disable();
+}
+
+/* Initialize the slowdown system for system calls in the system. */
+void slowdown_init(void)
+{
+	slowdown_active = true;
+
+	/* userprog/exception.c might have registered this interrupt already, take it over.
+	 */
+	intr_clear_int(0x01);
+
+	/* Add our registration. */
+	intr_register_int(0x01, 0, INTR_OFF, trap_interrupt, "Single-step trap");
+
+	/* Install our custom hook for the syscall handler as well. */
+	syscall_handler = intr_bypass_int(0x30, trap_syscall);
+}
+
+/* Enable slowdown for this thread. */
+void slowdown_enable(void)
+{
+	if (!slowdown_active)
+		return;
+
+	/* Set the trap flag so that we get an interrupt on every instruction. Only
+	 * applicable to the current thread. */
+	asm volatile("pushfl; movl %%esp, %%eax; orl $0x0100, (%%eax); popfl;" : : : "eax");
+}
+
+/* Disable slowdown for this thread. */
+void slowdown_disable(void)
+{
+	if (!slowdown_active)
+		return;
+
+	/* Set the trap flag so that we get an interrupt on every instruction. Only
+	 * applicable to the current thread. */
+	asm volatile("pushfl; movl %%esp, %%eax; andl $0xFFFFFEFF, (%%eax); popfl;"
+					 :
+					 :
+					 : "eax");
+}
+
+/* Check if slowdown is enabled for this thread. */
+bool slowdown_enabled(void)
+{
+	int flags;
+	asm volatile("pushfl; pop %0;" : "=g"(flags));
+	return flags & 0x0100;
+}
diff --git a/userprog/slowdown.h b/userprog/slowdown.h
new file mode 100644
index 0000000000000000000000000000000000000000..2e4bf5f9753ac9f0005d75bd06a0e7dc2f1b3017
--- /dev/null
+++ b/userprog/slowdown.h
@@ -0,0 +1,11 @@
+#ifndef USERPROG_SLOWDOWN_H
+#define USERPROG_SLOWDOWN_H
+
+#include <stdbool.h>
+
+void slowdown_init(void);
+void slowdown_enable(void);
+void slowdown_disable(void);
+bool slowdown_enabled(void);
+
+#endif
diff --git a/userprog/start_recursor b/userprog/start_recursor
new file mode 100644
index 0000000000000000000000000000000000000000..9e3c7e435e4d09828616f55cd1b2b01a0d1cdfb4
--- /dev/null
+++ b/userprog/start_recursor
@@ -0,0 +1,4 @@
+make -j
+time pintos -v -m 128 -k -T 60 qemu --filesys-size=4 \
+    -p ../examples/recursor_ng -a recursor_ng \
+    -- -q -f run 'recursor_ng pintosmaster 6 1'
diff --git a/userprog/start_recursor.sh b/userprog/start_recursor.sh
new file mode 100644
index 0000000000000000000000000000000000000000..9e3c7e435e4d09828616f55cd1b2b01a0d1cdfb4
--- /dev/null
+++ b/userprog/start_recursor.sh
@@ -0,0 +1,4 @@
+make -j
+time pintos -v -m 128 -k -T 60 qemu --filesys-size=4 \
+    -p ../examples/recursor_ng -a recursor_ng \
+    -- -q -f run 'recursor_ng pintosmaster 6 1'
diff --git a/userprog/syscall.c b/userprog/syscall.c
new file mode 100644
index 0000000000000000000000000000000000000000..647239b91c3c74249377d32196eae5da03490650
--- /dev/null
+++ b/userprog/syscall.c
@@ -0,0 +1,378 @@
+#include "userprog/syscall.h"
+#include "threads/interrupt.h"
+#include "threads/thread.h"
+
+#include <stdio.h>
+#include <syscall-nr.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <devices/timer.h>
+#include <devices/input.h>
+#include <userprog/process.h>
+#include "pagedir.h"
+#include "threads/vaddr.h"
+
+
+static void syscall_handler(struct intr_frame*);
+void syscall_init(void)
+{
+   intr_register_int(0x30, 3, INTR_ON, syscall_handler, "syscall");
+}
+
+static void syscall_handler(struct intr_frame* f ) //UNUSED
+{
+   
+	if (!is_buf_valid(f->esp,6)){
+		exit(-1);
+	}
+    // if( !is_valid(f->esp)|| !is_valid(f->esp+4)|| !is_valid(f->esp+8)) {
+    //     exit(-1);
+    // }
+
+
+    int operation = *(int*)f->esp;
+   
+    int fd ;            //= *(int*);
+    void *bf ;          //= *(void**);
+    unsigned size_ ;            //= *(int*);
+    void * file_name;    //= *(char*);
+    int status;
+    unsigned pos;
+    int ms ;
+    int pid;
+    const char* cmd_line;
+
+switch (operation)
+{
+   case SYS_HALT: /* (0) Halt the operating system. */
+        halt();
+        break;
+   case SYS_EXIT: /* (1) Terminate this process. */
+    if (!is_valid(f->esp+4)){
+        exit(-1);
+     }
+        status = *(int*)(f->esp+4);
+        exit(status);
+        break;
+   case SYS_EXEC: /* (2) Start another process. */
+        if (!is_valid(f->esp+4)){
+            exit(-1);
+         }
+        cmd_line = *(char**)(f->esp+4);
+        if(!is_str_valid(cmd_line)) {
+            exit(-1);
+            }    
+        f->eax = exec(cmd_line);
+        break;
+   case SYS_WAIT: /* (3) Wait for a child process to die. */
+        if (!is_valid(f->esp+4)){
+            exit(-1);
+        }
+        pid = *(int*)(f->esp+4);
+        f->eax = wait(pid);
+        break;
+   case SYS_CREATE:  /* (4) Create a file. */
+   if (!is_valid(f->esp+4) || !is_valid(f->esp+8)){
+        exit(-1);
+     }
+     file_name = *(char**)(f->esp+4);
+        if( !is_str_valid(file_name)) {
+            exit(-1);
+        }
+        size_ = *(int*)(f->esp+8);
+        f->eax = create(file_name,size_);
+        break;
+   case SYS_REMOVE: /* (5) Delete a file. */
+    if (!is_valid(f->esp+4)){
+        exit(-1);
+     }
+   file_name = *(char**)(f->esp+4);
+        if(!is_str_valid(file_name)) {
+            exit(-1);
+            }
+        f->eax = remove(file_name);
+        break;
+   case SYS_OPEN: /* (6) Open a file. */
+       if (!is_valid(f->esp+4)){
+        exit(-1);
+       }
+         file_name = *(void**)(f->esp+4);
+        if(!is_str_valid(file_name)) {
+            exit(-1);
+            }
+        f->eax = open(file_name);
+        break;
+   case SYS_FILESIZE: /* (7) Obtain a file's size. */
+        if (!is_valid(f->esp+4)){
+        exit(-1);
+     }
+        fd =*(int*)(f->esp+4);
+        if(!is_fd_valid(fd))  {
+            exit(-1);
+            }
+        f->eax = filesize(fd); 
+        break;
+   case SYS_READ: /* (8) Read from a file. */
+        if (!is_valid(f->esp+4) || !is_valid(f->esp+8) || !is_valid(f->esp+12)){
+        exit(-1);
+    	} 
+        fd =*(int*)(f->esp+4);
+        bf =*(void**)(f->esp+8);
+        size_ =*(unsigned*)(f->esp+12);
+		if(  !is_buf_valid(bf, size_) || !is_fd_valid(fd)){
+			exit(-1);
+		}
+        f->eax = read(fd,bf,size_);
+        break;
+   case SYS_WRITE: /* (9) Write to a file. */
+      if (!is_valid(f->esp+4) || !is_valid(f->esp+8) || !is_valid(f->esp+12)){
+        exit(-1);
+     } 
+        fd =*(int*)(f->esp+4);
+        bf =*(void**)(f->esp+8);
+        size_ =*(unsigned*)(f->esp+12);
+           if(  !is_buf_valid(bf, size_) || !is_fd_valid(fd)){
+                exit(-1);
+            }
+        f->eax = write(fd,bf,size_);
+        break;
+   case SYS_SEEK: /* (10) Change position in a file. */
+     if (!is_valid(f->esp+4) || !is_valid(f->esp+8)){
+        exit(-1);
+     } 
+        fd =*(int*)(f->esp+4);
+        pos =*(unsigned*)(f->esp+8);
+        if(!is_fd_valid(fd)){
+            exit(-1);
+            } 
+        seek( fd, pos);
+        break;
+   case SYS_TELL: /* (11) Report current position in a file. */
+         if (!is_valid(f->esp+4)){
+        exit(-1);
+     }
+         fd =*(int*)(f->esp+4);
+        if(!is_fd_valid(fd)){
+            exit(-1);
+            }
+        f->eax = tell( fd);
+        break;
+   case SYS_CLOSE: /* (12) Close a file. */
+         if (!is_valid(f->esp+4)){
+        exit(-1);
+     }
+        fd =*(int*)(f->esp+4);
+        if( !is_fd_valid(fd)){
+            exit(-1);
+            }
+        close(fd);
+        break;
+   case SYS_SLEEP: 
+  if (!is_valid(f->esp+4)){
+        exit(-1);
+     }
+        ms = *(int*)(f->esp+4);
+        sleep(ms);
+        break;
+   default:
+        exit(-1);
+        break;
+}
+}   
+    
+pid_t exec(const char* cmd_line){
+    pid_t pid = (pid_t) process_execute(cmd_line);
+    return pid;
+    }
+
+void sleep(int millis){
+   int64_t ml = (int64_t)millis;
+   timer_msleep(ml);
+}
+
+int wait(int pid){
+   tid_t tid = (tid_t)pid;
+   return process_wait(tid);
+}
+
+void halt (void ){
+   shutdown_power_off();
+}
+
+bool create (const char *file, unsigned initial_size){
+   return filesys_create(file, initial_size);
+}
+
+int open(const char *file){
+   struct thread *th = thread_current();
+   int fd;
+   if (filesys_open(file) == NULL){return -1;}
+   for(int i=0; i<128; i++){
+       if (th->file_descriptor[i].file==NULL){
+           th->file_descriptor[i].file = filesys_open(file);
+           th->file_descriptor[i].fd = i+2;
+           fd = th->file_descriptor[i].fd;
+           return fd;
+       }
+   }
+   return -1;
+}
+
+void close (int fd){
+    
+   struct thread *th = thread_current();
+   
+
+   char * file;
+   if(th->file_descriptor[fd-2].file != NULL){
+       file = th->file_descriptor[fd-2].file;
+       }
+
+   th->file_descriptor[fd-2].file = NULL;
+   th->file_descriptor[fd-2].fd = 0;
+   file_close(file);
+}
+
+bool remove (const char *file_name){
+   return filesys_remove(file_name);
+}
+
+void seek (int fd, unsigned position){
+   struct thread *th = thread_current();
+   
+
+   file_seek(th->file_descriptor[fd-2].file, position);
+}
+
+unsigned tell (int fd){
+   struct thread *th = thread_current();
+
+   return file_tell(th->file_descriptor[fd-2].file);
+}
+
+int filesize (int fd){
+   struct thread *th = thread_current();
+   
+
+
+   int file_size = (int) file_length(th->file_descriptor[fd-2].file);
+   return file_size;
+}
+
+int write (int fd, const void *buffer, unsigned size){
+   struct thread *th = thread_current();
+   
+   
+   if(fd == 1){
+       putbuf(buffer,size);
+       return size;
+   }
+
+   if(th->file_descriptor[fd-2].file != NULL){
+       
+       int fileWrite = (int) file_write(th->file_descriptor[fd-2].file, buffer, size);
+       return fileWrite;
+   }
+           
+return -1;
+
+}
+
+int read (int fd, void *buffer, unsigned size){
+
+struct thread *th = thread_current();
+
+   
+   if(fd == 0){
+       for(int i = 0; i<size;i++){
+       printf("%c",input_getc());
+       }
+       return size;
+   }
+
+   if(th->file_descriptor[fd-2].file != NULL){
+       int fileRead = (int) file_read(th->file_descriptor[fd-2].file, buffer, size);
+       return fileRead;
+   } 
+return -1;   
+}
+
+
+
+
+
+
+void exit (int status){
+   struct thread *th = thread_current();
+
+   if (th->current_child != NULL){
+      th->current_child->exit_status=status;//not sure
+   }
+
+    printf( "%s: exit(%d)\n" , thread_name() , status);
+   for(int i=0; i<128; i++){
+       close(th->file_descriptor[i].fd);
+       th->file_descriptor[i].file = NULL;
+       th->file_descriptor[i].fd = 0;
+   }
+
+   thread_exit();
+}
+
+bool is_valid(const void * value){
+    if ( value ==NULL ){
+        return false;
+    }
+    if ( is_user_vaddr(value) && pagedir_get_page(thread_current()->pagedir, value) != NULL ){
+            return true;
+    }
+    return false;
+}
+bool is_str_valid(const char *str_value) {
+    if (str_value == NULL) {
+        return false;
+    }
+    if (!is_valid(str_value)) {
+        return false;
+    }
+    while (*str_value != '\0') {
+        const char *temp = str_value;
+        if (!is_valid(temp)) {
+            return false;
+        }
+        str_value++;
+    }
+    return true;
+}
+
+
+bool is_buf_valid(void *buffer, unsigned size_of_buf){
+
+
+ if (buffer ==NULL){
+        return false;
+      }
+	if(!is_valid(buffer)) return false; 
+if(!is_valid(buffer+size_of_buf)) return false; 
+
+	
+        for (unsigned i = 0; i < size_of_buf; i++){ 	
+            if(i % PGSIZE == 0){
+                if(!is_valid(buffer + i)) return false; 
+                }
+                if(!is_user_vaddr(buffer + i)){
+                return false;
+            }
+
+	}
+	return true;
+}
+
+
+bool is_fd_valid(int fd){
+    if (fd<0 || fd >=128){
+        return false;
+    }
+    return true;
+}
+
diff --git a/userprog/syscall.h b/userprog/syscall.h
new file mode 100644
index 0000000000000000000000000000000000000000..eb75f6b675880200e9ba15b8dadbc4c1aa90ec77
--- /dev/null
+++ b/userprog/syscall.h
@@ -0,0 +1,55 @@
+#include <stdbool.h>
+#include "threads/interrupt.h"
+
+
+#ifndef USERPROG_SYSCALL_H
+#define USERPROG_SYSCALL_H
+
+typedef int pid_t;
+#define PID_ERROR ((pid_t) -1) /* Error value for pid_t. */
+
+
+void syscall_init(void);
+
+void sleep(int millis);
+void halt (void);
+bool create(const char *file, unsigned initial_size);
+int open (const char *file);
+void close (int fd);
+bool remove (const char *file_name);
+void seek (int fd, unsigned position);
+unsigned tell (int fd);
+int filesize (int fd);
+int write (int fd, const void *buffer, unsigned size);
+int read (int fd, void *buffer, unsigned size);
+void exit (int status);
+pid_t exec (const char *cmd_line);
+int wait(int pid);
+
+
+bool is_str_valid(const char * str_value);
+bool is_valid(const void * value);
+bool is_buf_valid(void * buffer, unsigned size_of_buf);
+
+bool is_fd_valid(int fd);
+
+#endif /* userprog/syscall.h */
+
+
+// #ifndef USERPROG_SYSCALL_H
+// #define USERPROG_SYSCALL_H
+// #include <stdbool.h>
+// #include "lib/user/syscall.h"
+
+// //Validation:
+// void validate_addr(void *addr);
+
+// //System calls:
+// void syscall_init(void);
+
+// unsigned sys_tell(int fd);
+// bool sys_create(const char* name, unsigned initial_size);
+// int sys_open (const char *file);
+// bool sys_remove(const char *file_name);
+
+// #endif /* userprog/syscall.h */
diff --git a/userprog/tss.c b/userprog/tss.c
new file mode 100644
index 0000000000000000000000000000000000000000..fd85c1436f409938105f022d71a768afe02cabce
--- /dev/null
+++ b/userprog/tss.c
@@ -0,0 +1,104 @@
+#include "userprog/tss.h"
+
+#include "threads/palloc.h"
+#include "threads/thread.h"
+#include "threads/vaddr.h"
+#include "userprog/gdt.h"
+
+#include <debug.h>
+#include <stddef.h>
+
+/* The Task-State Segment (TSS).
+
+	Instances of the TSS, an x86-specific structure, are used to
+	define "tasks", a form of support for multitasking built right
+	into the processor.  However, for various reasons including
+	portability, speed, and flexibility, most x86 OSes almost
+	completely ignore the TSS.  We are no exception.
+
+	Unfortunately, there is one thing that can only be done using
+	a TSS: stack switching for interrupts that occur in user mode.
+	When an interrupt occurs in user mode (ring 3), the processor
+	consults the ss0 and esp0 members of the current TSS to
+	determine the stack to use for handling the interrupt.  Thus,
+	we must create a TSS and initialize at least these fields, and
+	this is precisely what this file does.
+
+	When an interrupt is handled by an interrupt or trap gate
+	(which applies to all interrupts we handle), an x86 processor
+	works like this:
+
+	  - If the code interrupted by the interrupt is in the same
+		 ring as the interrupt handler, then no stack switch takes
+		 place.  This is the case for interrupts that happen when
+		 we're running in the kernel.  The contents of the TSS are
+		 irrelevant for this case.
+
+	  - If the interrupted code is in a different ring from the
+		 handler, then the processor switches to the stack
+		 specified in the TSS for the new ring.  This is the case
+		 for interrupts that happen when we're in user space.  It's
+		 important that we switch to a stack that's not already in
+		 use, to avoid corruption.  Because we're running in user
+		 space, we know that the current process's kernel stack is
+		 not in use, so we can always use that.  Thus, when the
+		 scheduler switches threads, it also changes the TSS's
+		 stack pointer to point to the new thread's kernel stack.
+		 (The call is in thread_schedule_tail() in thread.c.)
+
+	See [IA32-v3a] 6.2.1 "Task-State Segment (TSS)" for a
+	description of the TSS.  See [IA32-v3a] 5.12.1 "Exception- or
+	Interrupt-Handler Procedures" for a description of when and
+	how stack switching occurs during an interrupt. */
+struct tss {
+	uint16_t back_link, :16;
+	void* esp0;			 /* Ring 0 stack virtual address. */
+	uint16_t ss0, :16; /* Ring 0 stack segment selector. */
+	void* esp1;
+	uint16_t ss1, :16;
+	void* esp2;
+	uint16_t ss2, :16;
+	uint32_t cr3;
+	void (*eip)(void);
+	uint32_t eflags;
+	uint32_t eax, ecx, edx, ebx;
+	uint32_t esp, ebp, esi, edi;
+	uint16_t es, :16;
+	uint16_t cs, :16;
+	uint16_t ss, :16;
+	uint16_t ds, :16;
+	uint16_t fs, :16;
+	uint16_t gs, :16;
+	uint16_t ldt, :16;
+	uint16_t trace, bitmap;
+};
+
+/* Kernel TSS. */
+static struct tss* tss;
+
+/* Initializes the kernel TSS. */
+void tss_init(void)
+{
+	/* Our TSS is never used in a call gate or task gate, so only a
+		few fields of it are ever referenced, and those are the only
+		ones we initialize. */
+	tss = palloc_get_page(PAL_ASSERT | PAL_ZERO);
+	tss->ss0 = SEL_KDSEG;
+	tss->bitmap = 0xdfff;
+	tss_update();
+}
+
+/* Returns the kernel TSS. */
+struct tss* tss_get(void)
+{
+	ASSERT(tss != NULL);
+	return tss;
+}
+
+/* Sets the ring 0 stack pointer in the TSS to point to the end
+	of the thread stack. */
+void tss_update(void)
+{
+	ASSERT(tss != NULL);
+	tss->esp0 = (uint8_t*) thread_current() + PGSIZE;
+}
diff --git a/userprog/tss.h b/userprog/tss.h
new file mode 100644
index 0000000000000000000000000000000000000000..0756ae2c30cb7c9b825639b88560f0f4c4811623
--- /dev/null
+++ b/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/utils/.gitignore b/utils/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..de9191617e180fdfa8ad95ee3a354eb8b0a3cddb
--- /dev/null
+++ b/utils/.gitignore
@@ -0,0 +1,4 @@
+setitimer-helper
+squish-pty
+squish-unix
+qemu
diff --git a/utils/Makefile b/utils/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e38eb1f3dfa2972035eee486247e40e939a0be38
--- /dev/null
+++ b/utils/Makefile
@@ -0,0 +1,11 @@
+all: setitimer-helper squish-pty squish-unix
+
+CC = gcc
+CFLAGS = -Wall -W
+LOADLIBES = -lm
+setitimer-helper: setitimer-helper.o
+squish-pty: squish-pty.o
+squish-unix: squish-unix.o
+
+clean: 
+	rm -f *.o setitimer-helper squish-pty squish-unix
diff --git a/utils/Pintos.pm b/utils/Pintos.pm
new file mode 100644
index 0000000000000000000000000000000000000000..70df40d2309d234bd989667aa0f11647ba696969
--- /dev/null
+++ b/utils/Pintos.pm
@@ -0,0 +1,491 @@
+# Pintos helper subroutines.
+
+# Number of bytes available for the loader at the beginning of the MBR.
+# Kernel command-line arguments follow the loader.
+our $LOADER_SIZE = 314;
+
+# Partition types.
+my (%role2type) = (KERNEL => 0x20,
+		   FILESYS => 0x21,
+		   SCRATCH => 0x22,
+		   SWAP => 0x23);
+my (%type2role) = reverse %role2type;
+
+# Order of roles within a given disk.
+our (@role_order) = qw (KERNEL FILESYS SCRATCH SWAP);
+
+# Partitions.
+#
+# Valid keys are KERNEL, FILESYS, SCRATCH, SWAP.  Only those
+# partitions which are in use are included.
+#
+# Each value is a reference to a hash.  If the partition's contents
+# are to be obtained from a file (that will be copied into a new
+# virtual disk), then the hash contains:
+#
+# FILE => name of file from which the partition's contents are copied
+#         (perhaps "/dev/zero"),
+# OFFSET => offset in bytes in FILE,
+# BYTES => size in bytes of contents from FILE,
+#
+# If the partition is taken from a virtual disk directly, then it
+# contains the following.  The same keys are also filled in once a
+# file-based partition has been copied into a new virtual disk:
+#
+# DISK => name of virtual disk file,
+# START => sector offset of start of partition within DISK,
+# SECTORS => number of sectors of partition within DISK, which is usually
+#            greater than round_up (BYTES, 512) due to padding.
+our (%parts);
+
+# set_part($opt, $arg)
+#
+# For use as a helper function for Getopt::Long::GetOptions to set
+# disk sources.
+sub set_part {
+    my ($opt, $arg) = @_;
+    my ($role, $source) = $opt =~ /^([a-z]+)(?:-([a-z]+))?/ or die;
+
+    $role = uc $role;
+    $source = 'FILE' if $source eq '';
+
+    die "can't have two sources for \L$role\E partition"
+      if exists $parts{$role};
+
+    do_set_part ($role, $source, $arg);
+}
+
+# do_set_part($role, $source, $arg)
+#
+# Sets partition $role as coming from $source (one of 'file', 'from',
+# or 'size').  $arg is a file name for 'file' or 'from', a size in
+# megabytes for 'size'.
+sub do_set_part {
+    my ($role, $source, $arg) = @_;
+
+    my ($p) = $parts{$role} = {};
+    if ($source eq 'file') {
+	if (read_mbr ($arg)) {
+	    print STDERR "warning: $arg looks like a partitioned disk ";
+	    print STDERR "(did you want --$role-from=$arg or --disk=$arg?)\n"
+	}
+
+	$p->{FILE} = $arg;
+	$p->{OFFSET} = 0;
+	$p->{BYTES} = -s $arg;
+    } elsif ($source eq 'from') {
+	my (%pt) = read_partition_table ($arg);
+	my ($sp) = $pt{$role};
+	die "$arg: does not contain \L$role\E partition\n" if !defined $sp;
+
+	$p->{FILE} = $arg;
+	$p->{OFFSET} = $sp->{START} * 512;
+	$p->{BYTES} = $sp->{SECTORS} * 512;
+    } elsif ($source eq 'size') {
+	$arg =~ /^\d+(\.\d+)?|\.\d+$/ or die "$arg: not a valid size in MB\n";
+
+	$p->{FILE} = "/dev/zero";
+	$p->{OFFSET} = 0;
+	$p->{BYTES} = ceil ($arg * 1024 * 1024);
+    } else {
+	die;
+    }
+}
+
+# set_geometry('HEADS,SPT')
+# set_geometry('zip')
+#
+# For use as a helper function for Getopt::Long::GetOptions to set
+# disk geometry.
+sub set_geometry {
+    local ($_) = $_[1];
+    if ($_ eq 'zip') {
+	@geometry{'H', 'S'} = (64, 32);
+    } else {
+	@geometry{'H', 'S'} = /^(\d+)[,\s]+(\d+)$/
+	  or die "bad syntax for geometry\n";
+	$geometry{H} <= 255 or die "heads limited to 255\n";
+	$geometry{S} <= 63 or die "sectors per track limited to 63\n";
+    }
+}
+
+# set_align('bochs|full|none')
+#
+# For use as a helper function for Getopt::Long::GetOptions to set
+# partition alignment.
+sub set_align {
+    $align = $_[1];
+    die "unknown alignment type \"$align\"\n"
+      if $align ne 'bochs' && $align ne 'full' && $align ne 'none';
+}
+
+# assemble_disk(%args)
+#
+# Creates a virtual disk $args{DISK} containing the partitions
+# described by @args{KERNEL, FILESYS, SCRATCH, SWAP}.
+#
+# Required arguments:
+#   DISK => output disk file name
+#   HANDLE => output file handle (will be closed)
+#
+# Normally at least one of the following is included:
+#   KERNEL, FILESYS, SCRATCH, SWAP => {input:
+#				       FILE => file to read,
+#                                      OFFSET => byte offset in file,
+#                                      BYTES => byte count from file,
+#
+#                                      output:
+#				       DISK => output disk file name,
+#                                      START => sector offset in DISK,
+#                                      SECTORS => sector count in DISK},
+#
+# Optional arguments:
+#   ALIGN => 'bochs' (default), 'full', or 'none'
+#   GEOMETRY => {H => heads, S => sectors per track} (default 16, 63)
+#   FORMAT => 'partitioned' (default) or 'raw'
+#   LOADER => $LOADER_SIZE-byte string containing the loader binary
+#   ARGS => ['arg 1', 'arg 2', ...]
+sub assemble_disk {
+    my (%args) = @_;
+
+    my (%geometry) = $args{GEOMETRY} || (H => 16, S => 63);
+
+    my ($align);	# Align partition start, end to cylinder boundary?
+    my ($pad);		# Pad end of disk out to cylinder boundary?
+    if (!defined ($args{ALIGN}) || $args{ALIGN} eq 'bochs') {
+	$align = 0;
+	$pad = 1;
+    } elsif ($args{ALIGN} eq 'full') {
+	$align = 1;
+	$pad = 0;
+    } elsif ($args{ALIGN} eq 'none') {
+	$align = $pad = 0;
+    } else {
+	die;
+    }
+
+    my ($format) = $args{FORMAT} || 'partitioned';
+    die if $format ne 'partitioned' && $format ne 'raw';
+
+    # Check that we have apartitions to copy in.
+    my $part_cnt = grep (defined ($args{$_}), keys %role2type);
+    die "must have exactly one partition for raw output\n"
+      if $format eq 'raw' && $part_cnt != 1;
+
+    # Calculate the disk size.
+    my ($total_sectors) = 0;
+    if ($format eq 'partitioned') {
+	$total_sectors += $align ? $geometry{S} : 1;
+    }
+    for my $role (@role_order) {
+	my ($p) = $args{$role};
+	next if !defined $p;
+
+	die if $p->{DISK};
+
+	my ($bytes) = $p->{BYTES};
+	my ($start) = $total_sectors;
+	my ($end) = $start + div_round_up ($bytes, 512);
+	$end = round_up ($end, cyl_sectors (%geometry)) if $align;
+
+	$p->{DISK} = $args{DISK};
+	$p->{START} = $start;
+	$p->{SECTORS} = $end - $start;
+	$total_sectors = $end;
+    }
+
+    # Write the disk.
+    my ($disk_fn) = $args{DISK};
+    my ($disk) = $args{HANDLE};
+    if ($format eq 'partitioned') {
+	# Pack loader into MBR.
+	my ($loader) = $args{LOADER} || "\xcd\x18";
+	my ($mbr) = pack ("a$LOADER_SIZE", $loader);
+
+	$mbr .= make_kernel_command_line (@{$args{ARGS}});
+
+	# Pack partition table into MBR.
+	$mbr .= make_partition_table (\%geometry, \%args);
+
+	# Add signature to MBR.
+	$mbr .= pack ("v", 0xaa55);
+
+	die if length ($mbr) != 512;
+	write_fully ($disk, $disk_fn, $mbr);
+	write_zeros ($disk, $disk_fn, 512 * ($geometry{S} - 1)) if $align;
+    }
+    for my $role (@role_order) {
+	my ($p) = $args{$role};
+	next if !defined $p;
+
+	my ($source);
+	my ($fn) = $p->{FILE};
+	open ($source, '<', $fn) or die "$fn: open: $!\n";
+	if ($p->{OFFSET}) {
+	    sysseek ($source, $p->{OFFSET}, 0) == $p->{OFFSET}
+	      or die "$fn: seek: $!\n";
+	}
+	copy_file ($source, $fn, $disk, $disk_fn, $p->{BYTES});
+	close ($source) or die "$fn: close: $!\n";
+
+	write_zeros ($disk, $disk_fn, $p->{SECTORS} * 512 - $p->{BYTES});
+    }
+    if ($pad) {
+	my ($pad_sectors) = round_up ($total_sectors, cyl_sectors (%geometry));
+	write_zeros ($disk, $disk_fn, ($pad_sectors - $total_sectors) * 512);
+    }
+    close ($disk) or die "$disk: close: $!\n";
+}
+
+# make_partition_table({H => heads, S => sectors}, {KERNEL => ..., ...})
+#
+# Creates and returns a partition table for the given partitions and
+# disk geometry.
+sub make_partition_table {
+    my ($geometry, $partitions) = @_;
+    my ($table) = '';
+    for my $role (@role_order) {
+	defined (my $p = $partitions->{$role}) or next;
+
+	my $end = $p->{START} + $p->{SECTORS} - 1;
+	my $bootable = $role eq 'KERNEL';
+
+	$table .= pack ("C", $bootable ? 0x80 : 0);   # Bootable?
+	$table .= pack_chs ($p->{START}, $geometry);  # CHS of partition start
+	$table .= pack ("C", $role2type{$role});      # Partition type
+	$table .= pack_chs($end, $geometry);          # CHS of partition end
+	$table .= pack ("V", $p->{START});            # LBA of partition start
+	$table .= pack ("V", $p->{SECTORS});          # Length in sectors
+	die if length ($table) % 16;
+    }
+    return pack ("a64", $table);
+}
+
+# make_kernel_command_line(@args)
+#
+# Returns the raw bytes to write to an MBR at offset $LOADER_SIZE to
+# set a Pintos kernel command line.
+sub make_kernel_command_line {
+    my (@args) = @_;
+    my ($args) = join ('', map ("$_\0", @args));
+    die "command line exceeds 128 bytes" if length ($args) > 128;
+    return pack ("V a128", scalar (@args), $args);
+}
+
+# copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size)
+#
+# Copies $size bytes from $from_handle to $to_handle.
+# $from_file_name and $to_file_name are used in error messages.
+sub copy_file {
+    my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_;
+
+    while ($size > 0) {
+	my ($chunk_size) = 4096;
+	$chunk_size = $size if $chunk_size > $size;
+	$size -= $chunk_size;
+
+	my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size);
+	write_fully ($to_handle, $to_file_name, $data);
+    }
+}
+
+# read_fully($handle, $file_name, $bytes)
+#
+# Reads exactly $bytes bytes from $handle and returns the data read.
+# $file_name is used in error messages.
+sub read_fully {
+    my ($handle, $file_name, $bytes) = @_;
+    my ($data);
+    my ($read_bytes) = sysread ($handle, $data, $bytes);
+    die "$file_name: read: $!\n" if !defined $read_bytes;
+    die "$file_name: unexpected end of file\n" if $read_bytes != $bytes;
+    return $data;
+}
+
+# write_fully($handle, $file_name, $data)
+#
+# Write $data to $handle.
+# $file_name is used in error messages.
+sub write_fully {
+    my ($handle, $file_name, $data) = @_;
+    my ($written_bytes) = syswrite ($handle, $data);
+    die "$file_name: write: $!\n" if !defined $written_bytes;
+    die "$file_name: short write\n" if $written_bytes != length $data;
+}
+
+sub write_zeros {
+    my ($handle, $file_name, $size) = @_;
+
+    while ($size > 0) {
+	my ($chunk_size) = 4096;
+	$chunk_size = $size if $chunk_size > $size;
+	$size -= $chunk_size;
+
+	write_fully ($handle, $file_name, "\0" x $chunk_size);
+    }
+}
+
+# div_round_up($x,$y)
+#
+# Returns $x / $y, rounded up to the nearest integer.
+# $y must be an integer.
+sub div_round_up {
+    my ($x, $y) = @_;
+    return int ((ceil ($x) + $y - 1) / $y);
+}
+
+# round_up($x, $y)
+#
+# Returns $x rounded up to the nearest multiple of $y.
+# $y must be an integer.
+sub round_up {
+    my ($x, $y) = @_;
+    return div_round_up ($x, $y) * $y;
+}
+
+# cyl_sectors(H => heads, S => sectors)
+#
+# Returns the number of sectors in a cylinder of a disk with the given
+# geometry.
+sub cyl_sectors {
+    my (%geometry) = @_;
+    return $geometry{H} * $geometry{S};
+}
+
+# read_loader($file_name)
+#
+# Reads and returns the first $LOADER_SIZE bytes in $file_name.
+# If $file_name is undefined, tries to find the default loader.
+# Makes sure that the loader is a reasonable size.
+sub read_loader {
+    my ($name) = @_;
+    $name = find_file ("loader.bin") if !defined $name;
+    die "Cannot find loader\n" if !defined $name;
+
+    my ($handle);
+    open ($handle, '<', $name) or die "$name: open: $!\n";
+    -s $handle == $LOADER_SIZE || -s $handle == 512
+      or die "$name: must be exactly $LOADER_SIZE or 512 bytes long\n";
+    $loader = read_fully ($handle, $name, $LOADER_SIZE);
+    close ($handle) or die "$name: close: $!\n";
+    return $loader;
+}
+
+# pack_chs($lba, {H => heads, S => sectors})
+#
+# Converts logical sector $lba to a 3-byte packed geometrical sector
+# in the format used in PC partition tables (see [Partitions]) and
+# returns the geometrical sector as a 3-byte string.
+sub pack_chs {
+    my ($lba, $geometry) = @_;
+    my ($cyl, $head, $sect) = lba_to_chs ($lba, $geometry);
+    return pack ("CCC", $head, $sect | (($cyl >> 2) & 0xc0), $cyl & 0xff);
+}
+
+# lba_to_chs($lba, {H => heads, S => sectors})
+#
+# Returns the geometrical sector corresponding to logical sector $lba
+# given the specified geometry.
+sub lba_to_chs {
+    my ($lba, $geometry) = @_;
+    my ($hpc) = $geometry->{H};
+    my ($spt) = $geometry->{S};
+
+    # Source:
+    # http://en.wikipedia.org/wiki/CHS_conversion
+    use integer;
+    my $cyl = $lba / ($hpc * $spt);
+    my $temp = $lba % ($hpc * $spt);
+    my $head = $temp / $spt;
+    my $sect = $temp % $spt + 1;
+
+    # Source:
+    # http://www.cgsecurity.org/wiki/Intel_Partition_Table
+    if ($cyl <= 1023) {
+        return ($cyl, $head, $sect);
+    } else {
+        return (1023, 254, 63);	## or should this be (1023, $hpc, $spt)?
+    }
+}
+
+# read_mbr($file)
+#
+# Tries to read an MBR from $file.  Returns the 512-byte MBR if
+# successful, otherwise numeric 0.
+sub read_mbr {
+    my ($file) = @_;
+    my ($retval) = 0;
+    open (FILE, '<', $file) or die "$file: open: $!\n";
+    if (-s FILE == 0) {
+	die "$file: file has zero size\n";
+    } elsif (-s FILE >= 512) {
+	my ($mbr);
+	sysread (FILE, $mbr, 512) == 512 or die "$file: read: $!\n";
+	$retval = $mbr if unpack ("v", substr ($mbr, 510)) == 0xaa55;
+    }
+    close (FILE);
+    return $retval;
+}
+
+# interpret_partition_table($mbr, $disk)
+#
+# Parses the partition-table in the specified 512-byte $mbr and
+# returns the partitions.  $disk is used for error messages.
+sub interpret_partition_table {
+    my ($mbr, $disk) = @_;
+    my (%parts);
+    for my $i (0...3) {
+	my ($bootable, $valid, $type, $lba_start, $lba_length)
+	  = unpack ("C X V C x3 V V", substr ($mbr, 446 + 16 * $i, 16));
+	next if !$valid;
+
+	(print STDERR "warning: invalid partition entry $i in $disk\n"),
+	  next if $bootable != 0 && $bootable != 0x80;
+
+	my ($role) = $type2role{$type};
+	(printf STDERR "warning: non-Pintos partition type 0x%02x in %s\n",
+	 $type, $disk),
+	  next if !defined $role;
+
+	(print STDERR "warning: duplicate \L$role\E partition in $disk\n"),
+	  next if exists $parts{$role};
+
+	$parts{$role} = {START => $lba_start,
+			 SECTORS => $lba_length};
+    }
+    return %parts;
+}
+
+# find_file($base_name)
+#
+# Looks for a file named $base_name in a couple of likely spots.  If
+# found, returns the name; otherwise, returns undef.
+sub find_file {
+    my ($base_name) = @_;
+    -e && return $_ foreach $base_name, "build/$base_name";
+    return undef;
+}
+
+# read_partition_table($file)
+#
+# Reads a partition table from $file and returns the parsed
+# partitions.  Dies if partitions can't be read.
+sub read_partition_table {
+    my ($file) = @_;
+    my ($mbr) = read_mbr ($file);
+    die "$file: not a partitioned disk\n" if !$mbr;
+    return interpret_partition_table ($mbr, $file);
+}
+
+# max(@args)
+#
+# Returns the numerically largest value in @args.
+sub max {
+    my ($max) = $_[0];
+    foreach (@_[1..$#_]) {
+	$max = $_ if $_ > $max;
+    }
+    return $max;
+}
+
+1;
diff --git a/utils/backtrace b/utils/backtrace
new file mode 100644
index 0000000000000000000000000000000000000000..95e422f04793610960414529f543ae6d82132c6c
--- /dev/null
+++ b/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/utils/gdb-macros b/utils/gdb-macros
new file mode 100644
index 0000000000000000000000000000000000000000..3babb52747f7689333aee64ffac75adb06471a66
--- /dev/null
+++ b/utils/gdb-macros
@@ -0,0 +1,140 @@
+#
+# A set of useful macros that can help debug Pintos.
+#
+# Include with "source" cmd in gdb.
+# Use "help user-defined" for help.
+#
+# Author: Godmar Back <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 backtraces of all threads (based on 'all_list' all threads list)
+define btthreadall
+    btthreadlist &all_list allelem
+end
+document btthreadall
+    Print backtraces of all threads
+end
+
+# print a correct backtrace by adjusting $eip
+# this works best right at intr0e_stub
+define btpagefault
+    set $saveeip = $eip
+    set $eip = ((void**)$esp)[1]
+    backtrace
+    set $eip = $saveeip
+end
+document btpagefault
+    Print a backtrace of the current thread after a pagefault
+end
+
+# invoked whenever the program stops
+define hook-stop
+    # stopped at stub #0E = #14 (page fault exception handler stub)
+    if ($eip == intr0e_stub)
+        set $savedeip = ((void**)$esp)[1]
+        # if this was in user mode, the OS should handle it
+        # either handle the page fault or terminate the process
+        if ($savedeip < 0xC0000000)
+            printf "pintos-debug: a page fault exception occurred in user mode\n"
+            printf "pintos-debug: hit 'c' to continue, or 's' to step to intr_handler\n"
+        else
+            # if this was in kernel mode, a stack trace might be useful
+            printf "pintos-debug: a page fault occurred in kernel mode\n"
+            btpagefault
+        end
+    end
+end
+
+# load symbols for a Pintos user program
+define loadusersymbols
+    shell objdump -h $arg0 | awk '/.text/ { print "add-symbol-file $arg0 0x"$4 }' > .loadsymbols
+    source .loadsymbols
+    shell rm -f .loadsymbols
+end
+document loadusersymbols
+    Load the symbols contained in a user program's executable.
+    Example:
+        loadusersymbols tests/userprog/exec-multiple
+end
+
+define debugpintos
+    target remote localhost:1234
+end
+document debugpintos
+    Attach debugger to pintos process
+end
diff --git a/utils/pintos b/utils/pintos
new file mode 100644
index 0000000000000000000000000000000000000000..603cd4133183cd121480e4b8d7cbaf4326bc42eb
--- /dev/null
+++ b/utils/pintos
@@ -0,0 +1,964 @@
+#! /usr/bin/perl -w
+
+use strict;
+use POSIX;
+use Fcntl;
+use File::Temp 'tempfile';
+use Getopt::Long qw(:config bundling);
+use Fcntl qw(SEEK_SET SEEK_CUR);
+
+# Read Pintos.pm from the same directory as this program.
+BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
+
+# Command-line options.
+our ($start_time) = time ();
+our ($sim);			# Simulator: bochs, qemu, or player.
+our ($debug) = "none";		# Debugger: none, monitor, or gdb.
+our ($mem) = 4;			# Physical RAM in MB.
+our ($serial) = 1;		# Use serial port for input and output?
+our ($vga);			# VGA output: window, terminal, or none.
+our ($jitter);			# Seed for random timer interrupts, if set.
+our ($realtime);		# Synchronize timer interrupts with real time?
+our ($timeout);			# Maximum runtime in seconds, if set.
+our ($kill_on_failure);		# Abort quickly on test failure?
+our (@puts);			# Files to copy into the VM.
+our (@gets);			# Files to copy out of the VM.
+our ($as_ref);			# Reference to last addition to @gets or @puts.
+our (@kernel_args);		# Arguments to pass to kernel.
+our (%parts);			# Partitions.
+our ($make_disk);		# Name of disk to create.
+our ($tmp_disk) = 1;		# Delete $make_disk after run?
+our (@disks);			# Extra disk images to pass to simulator.
+our ($loader_fn);		# Bootstrap loader.
+our (%geometry);		# IDE disk geometry.
+our ($align);			# Partition alignment.
+our ($gdb_port) = $ENV{"GDB_PORT"} || "1234"; # Port to listen on for GDB
+
+parse_command_line ();
+prepare_scratch_disk ();
+find_disks ();
+run_vm ();
+finish_scratch_disk ();
+
+exit 0;
+
+# Parses the command line.
+sub parse_command_line {
+    usage (0) if @ARGV == 0 || (@ARGV == 1 && $ARGV[0] eq '--help');
+
+    @kernel_args = @ARGV;
+    if (grep ($_ eq '--', @kernel_args)) {
+	@ARGV = ();
+	while ((my $arg = shift (@kernel_args)) ne '--') {
+	    push (@ARGV, $arg);
+	}
+	GetOptions ("sim=s" => sub { set_sim ($_[1]) },
+		    "bochs" => sub { set_sim ("bochs") },
+		    "qemu" => sub { set_sim ("qemu") },
+		    "player" => sub { set_sim ("player") },
+
+		    "debug=s" => sub { set_debug ($_[1]) },
+		    "no-debug" => sub { set_debug ("none") },
+		    "monitor" => sub { set_debug ("monitor") },
+		    "gdb" => sub { set_debug ("gdb") },
+
+		    "m|memory=i" => \$mem,
+		    "j|jitter=i" => sub { set_jitter ($_[1]) },
+		    "r|realtime" => sub { set_realtime () },
+
+		    "T|timeout=i" => \$timeout,
+		    "k|kill-on-failure" => \$kill_on_failure,
+
+		    "v|no-vga" => sub { set_vga ('none'); },
+		    "s|no-serial" => sub { $serial = 0; },
+		    "t|terminal" => sub { set_vga ('terminal'); },
+            "w|window" => sub { set_vga ('vga'); },
+
+		    "p|put-file=s" => sub { add_file (\@puts, $_[1]); },
+		    "g|get-file=s" => sub { add_file (\@gets, $_[1]); },
+		    "a|as=s" => sub { set_as ($_[1]); },
+
+		    "h|help" => sub { usage (0); },
+
+		    "kernel=s" => \&set_part,
+		    "filesys=s" => \&set_part,
+		    "swap=s" => \&set_part,
+
+		    "filesys-size=s" => \&set_part,
+		    "scratch-size=s" => \&set_part,
+		    "swap-size=s" => \&set_part,
+
+		    "kernel-from=s" => \&set_part,
+		    "filesys-from=s" => \&set_part,
+		    "swap-from=s" => \&set_part,
+
+		    "make-disk=s" => sub { $make_disk = $_[1];
+					   $tmp_disk = 0; },
+		    "disk=s" => sub { set_disk ($_[1]); },
+		    "loader=s" => \$loader_fn,
+
+		    "geometry=s" => \&set_geometry,
+		    "align=s" => \&set_align)
+	  or exit 1;
+    }
+
+    $sim = "qemu" if !defined $sim;
+    $debug = "none" if !defined $debug;
+    $vga = "none" if !defined $vga;
+
+    undef $timeout, print "warning: disabling timeout with --$debug\n"
+      if defined ($timeout) && $debug ne 'none';
+
+    print "warning: enabling serial port for -k or --kill-on-failure\n"
+      if $kill_on_failure && !$serial;
+
+    $align = "bochs",
+      print STDERR "warning: setting --align=bochs for Bochs support\n"
+	if $sim eq 'bochs' && defined ($align) && $align eq 'none';
+
+    $kill_on_failure = 0;
+}
+
+# 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:
+  --qemu                   (default) Use QEMU as simulator
+  --bochs                  Use Bochs 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             (default) No VGA display or keyboard
+  -s, --no-serial          No serial input or output
+  -t, --terminal           Display VGA in terminal (Bochs only)
+  -w, --window             Display simulator window
+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:
+  -p, --put-file=HOSTFN    Copy HOSTFN into VM, by default under same name
+  -g, --get-file=GUESTFN   Copy GUESTFN out of VM, by default under same name
+  -a, --as=FILENAME        Specifies guest (for -p) or host (for -g) file name
+Partition options: (where PARTITION is one of: kernel filesys scratch swap)
+  --PARTITION=FILE         Use a copy of FILE for the given PARTITION
+  --PARTITION-size=SIZE    Create an empty PARTITION of the given SIZE in MB
+  --PARTITION-from=DISK    Use of a copy of the given PARTITION in DISK
+  (There is no --kernel-size, --scratch, or --scratch-from option.)
+Disk configuration options:
+  --make-disk=DISK         Name the new DISK and don't delete it after the run
+  --disk=DISK              Also use existing DISK (may be used multiple times)
+Advanced disk configuration options:
+  --loader=FILE            Use FILE as bootstrap loader (default: loader.bin)
+  --geometry=H,S           Use H head, S sector geometry (default: 16,63)
+  --geometry=zip           Use 64 head, 32 sector geometry for USB-ZIP boot
+                           (see http://syslinux.zytor.com/usbkey.php)
+  --align=bochs            Pad out disk to cylinder to support Bochs (default)
+  --align=full             Align partition boundaries to cylinder boundary to
+                           let fdisk guess correct geometry and quiet warnings
+  --align=none             Don't align partitions at all, to save space
+Other options:
+  -h, --help               Display this help message.
+EOF
+    exit $exitcode;
+}
+
+# Sets the simulator.
+sub set_sim {
+    my ($new_sim) = @_;
+    die "--$new_sim conflicts with --$sim\n"
+	if defined ($sim) && $sim ne $new_sim;
+    $sim = $new_sim;
+}
+
+# Sets the debugger.
+sub set_debug {
+    my ($new_debug) = @_;
+    die "--$new_debug conflicts with --$debug\n"
+	if $debug ne 'none' && $new_debug ne 'none' && $debug ne $new_debug;
+    $debug = $new_debug;
+}
+
+# Sets VGA output destination.
+sub set_vga {
+    my ($new_vga) = @_;
+    if (defined ($vga) && $vga ne $new_vga) {
+	print "warning: conflicting vga display options\n";
+    }
+    $vga = $new_vga;
+}
+
+# Sets randomized timer interrupts.
+sub set_jitter {
+    my ($new_jitter) = @_;
+    die "--realtime conflicts with --jitter\n" if defined $realtime;
+    die "different --jitter already defined\n"
+	if defined $jitter && $jitter != $new_jitter;
+    $jitter = $new_jitter;
+}
+
+# Sets real-time timer interrupts.
+sub set_realtime {
+    die "--realtime conflicts with --jitter\n" if defined $jitter;
+    $realtime = 1;
+}
+
+# add_file(\@list, $file)
+#
+# Adds [$file] to @list, which should be @puts or @gets.
+# Sets $as_ref to point to the added element.
+sub add_file {
+    my ($list, $file) = @_;
+    $as_ref = [$file];
+    push (@$list, $as_ref);
+}
+
+# Sets the guest/host name for the previous put/get.
+sub set_as {
+    my ($as) = @_;
+    die "-a (or --as) is only allowed after -p or -g\n" if !defined $as_ref;
+    die "Only one -a (or --as) is allowed after -p or -g\n"
+      if defined $as_ref->[1];
+    $as_ref->[1] = $as;
+}
+
+# Sets $disk as a disk to be included in the VM to run.
+sub set_disk {
+    my ($disk) = @_;
+
+    push (@disks, $disk);
+
+    my (%pt) = read_partition_table ($disk);
+    for my $role (keys %pt) {
+	die "can't have two sources for \L$role\E partition"
+	  if exists $parts{$role};
+	$parts{$role}{DISK} = $disk;
+	$parts{$role}{START} = $pt{$role}{START};
+	$parts{$role}{SECTORS} = $pt{$role}{SECTORS};
+    }
+}
+
+# Locates the files used to back each of the virtual disks,
+# and creates temporary disks.
+sub find_disks {
+    # Find kernel, if we don't already have one.
+    if (!exists $parts{KERNEL}) {
+	my $name = find_file ('kernel.bin');
+	die "Cannot find kernel\n" if !defined $name;
+	do_set_part ('KERNEL', 'file', $name);
+    }
+
+    # Try to find file system and swap disks, if we don't already have
+    # partitions.
+    if (!exists $parts{FILESYS}) {
+	my $name = find_file ('filesys.dsk');
+	set_disk ($name) if defined $name;
+    }
+    if (!exists $parts{SWAP}) {
+	my $name = find_file ('swap.dsk');
+	set_disk ($name) if defined $name;
+    }
+
+    # Warn about (potentially) missing partitions.
+    if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
+	if ((grep ($project eq $_, qw (userprog vm filesys)))
+	    && !defined $parts{FILESYS}) {
+	    print STDERR "warning: it looks like you're running the $project ";
+	    print STDERR "project, but no file system partition is present\n";
+	}
+	if ($project eq 'vm' && !defined $parts{SWAP}) {
+	    print STDERR "warning: it looks like you're running the $project ";
+	    print STDERR "project, but no swap partition is present\n";
+	}
+    }
+
+    # Open disk handle.
+    my ($handle);
+    if (!defined $make_disk) {
+	($handle, $make_disk) = tempfile (UNLINK => $tmp_disk,
+					  SUFFIX => '.dsk');
+    } else {
+	die "$make_disk: already exists\n" if -e $make_disk;
+	open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n";
+    }
+
+    # Prepare the arguments to pass to the Pintos kernel.
+    my (@args);
+    push (@args, shift (@kernel_args))
+      while @kernel_args && $kernel_args[0] =~ /^-/;
+    push (@args, 'extract') if @puts;
+    push (@args, @kernel_args);
+    push (@args, 'append', $_->[0]) foreach @gets;
+
+    # Make disk.
+    my (%disk);
+    our (@role_order);
+    for my $role (@role_order) {
+	my $p = $parts{$role};
+	next if !defined $p;
+	next if exists $p->{DISK};
+	$disk{$role} = $p;
+    }
+    $disk{DISK} = $make_disk;
+    $disk{HANDLE} = $handle;
+    $disk{ALIGN} = $align;
+    $disk{GEOMETRY} = %geometry;
+    $disk{FORMAT} = 'partitioned';
+    $disk{LOADER} = read_loader ($loader_fn);
+    $disk{ARGS} = \@args;
+    assemble_disk (%disk);
+
+    # Put the disk at the front of the list of disks.
+    unshift (@disks, $make_disk);
+    die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4;
+}
+
+# Prepare the scratch disk for gets and puts.
+sub prepare_scratch_disk {
+    return if !@gets && !@puts;
+
+    my ($p) = $parts{SCRATCH};
+    # Create temporary partition and write the files to put to it,
+    # then write an end-of-archive marker.
+    my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part');
+    put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0],
+		      $part_handle, $part_fn)
+      foreach @puts;
+    write_fully ($part_handle, $part_fn, "\0" x 1024);
+
+    # Make sure the scratch disk is big enough to get big files
+    # and at least as big as any requested size.
+    my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512);
+    extend_file ($part_handle, $part_fn, $size);
+    close ($part_handle);
+
+    if (exists $p->{DISK}) {
+	# Copy the scratch partition to the disk.
+	die "$p->{DISK}: scratch partition too small\n"
+	  if $p->{SECTORS} * 512 < $size;
+
+	my ($disk_handle);
+	open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n";
+	open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n";
+	my ($start) = $p->{START} * 512;
+	sysseek ($disk_handle, $start, SEEK_SET) == $start
+	  or die "$p->{DISK}: seek: $!\n";
+	copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size);
+	close ($disk_handle) or die "$p->{DISK}: close: $!\n";
+	close ($part_handle) or die "$part_fn: close: $!\n";
+    } else {
+	# Set $part_fn as the source for the scratch partition.
+	do_set_part ('SCRATCH', 'file', $part_fn);
+    }
+}
+
+# Read "get" files from the scratch disk.
+sub finish_scratch_disk {
+    return if !@gets;
+
+    # Open scratch partition.
+    my ($p) = $parts{SCRATCH};
+    my ($part_handle);
+    my ($part_fn) = $p->{DISK};
+    open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n";
+    sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512
+      or die "$part_fn: seek: $!\n";
+
+    # Read each file.
+    # If reading fails, delete that file and all subsequent files, but
+    # don't die with an error, because that's a guest error not a host
+    # error.  (If we do exit with an error code, it fouls up the
+    # grading process.)  Instead, just make sure that the host file(s)
+    # we were supposed to retrieve is unlinked.
+    my ($ok) = 1;
+    my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512;
+    foreach my $get (@gets) {
+	my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0];
+	if ($ok) {
+	    my ($error) = get_scratch_file ($name, $part_handle, $part_fn);
+	    if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) {
+		$error = "$part_fn: scratch data overflows partition";
+	    }
+	    if ($error) {
+		print STDERR "getting $name failed ($error)\n";
+		$ok = 0;
+	    }
+	}
+	die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT};
+    }
+}
+
+# mk_ustar_field($number, $size)
+#
+# Returns $number in a $size-byte numeric field in the format used by
+# the standard ustar archive header.
+sub mk_ustar_field {
+    my ($number, $size) = @_;
+    my ($len) = $size - 1;
+    my ($out) = sprintf ("%0${len}o", $number) . "\0";
+    die "$number: too large for $size-byte octal ustar field\n"
+      if length ($out) != $size;
+    return $out;
+}
+
+# calc_ustar_chksum($s)
+#
+# Calculates and returns the ustar checksum of 512-byte ustar archive
+# header $s.
+sub calc_ustar_chksum {
+    my ($s) = @_;
+    die if length ($s) != 512;
+    substr ($s, 148, 8, ' ' x 8);
+    return unpack ("%32a*", $s);
+}
+
+# put_scratch_file($src_file_name, $dst_file_name,
+#                  $disk_handle, $disk_file_name).
+#
+# Copies $src_file_name into $disk_handle for extraction as
+# $dst_file_name.  $disk_file_name is used for error messages.
+sub put_scratch_file {
+    my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_;
+
+    print "Copying $src_file_name to scratch partition...\n";
+
+    # ustar format supports up to 100 characters for a file name, and
+    # even longer names given some common properties, but our code in
+    # the Pintos kernel only supports at most 99 characters.
+    die "$dst_file_name: name too long (max 99 characters)\n"
+      if length ($dst_file_name) > 99;
+
+    # Compose and write ustar header.
+    stat $src_file_name or die "$src_file_name: stat: $!\n";
+    my ($size) = -s _;
+    my ($header) = (pack ("a100", $dst_file_name)	# name
+		    . mk_ustar_field (0644, 8)		# mode
+		    . mk_ustar_field (0, 8)		# uid
+		    . mk_ustar_field (0, 8)		# gid
+		    . mk_ustar_field ($size, 12)	# size
+		    . mk_ustar_field (1136102400, 12)	# mtime
+		    . (' ' x 8)				# chksum
+		    . '0'				# typeflag
+		    . ("\0" x 100)			# linkname
+		    . "ustar\0"				# magic
+		    . "00"				# version
+		    . "root" . ("\0" x 28)		# uname
+		    . "root" . ("\0" x 28)		# gname
+		    . "\0" x 8				# devmajor
+		    . "\0" x 8				# devminor
+		    . ("\0" x 155))			# prefix
+                    . "\0" x 12;			# pad to 512 bytes
+    substr ($header, 148, 8) = mk_ustar_field (calc_ustar_chksum ($header), 8);
+    write_fully ($disk_handle, $disk_file_name, $header);
+
+    # Copy file data.
+    my ($put_handle);
+    sysopen ($put_handle, $src_file_name, O_RDONLY)
+      or die "$src_file_name: open: $!\n";
+    copy_file ($put_handle, $src_file_name, $disk_handle, $disk_file_name,
+	       $size);
+    die "$src_file_name: changed size while being read\n"
+      if $size != -s $put_handle;
+    close ($put_handle);
+
+    # Round up disk data to beginning of next sector.
+    write_fully ($disk_handle, $disk_file_name, "\0" x (512 - $size % 512))
+      if $size % 512;
+}
+
+# get_scratch_file($get_file_name, $disk_handle, $disk_file_name)
+#
+# Copies from $disk_handle to $get_file_name (which is created).
+# $disk_file_name is used for error messages.
+# Returns 1 if successful, 0 on failure.
+sub get_scratch_file {
+    my ($get_file_name, $disk_handle, $disk_file_name) = @_;
+
+    print "Copying $get_file_name out of $disk_file_name...\n";
+
+    # Read ustar header sector.
+    my ($header) = read_fully ($disk_handle, $disk_file_name, 512);
+    return "scratch disk tar archive ends unexpectedly"
+      if $header eq ("\0" x 512);
+
+    # Verify magic numbers.
+    return "corrupt ustar signature" if substr ($header, 257, 6) ne "ustar\0";
+    return "invalid ustar version" if substr ($header, 263, 2) ne '00';
+
+    # Verify checksum.
+    my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8)));
+    my ($correct_chksum) = calc_ustar_chksum ($header);
+    return "checksum mismatch" if $chksum != $correct_chksum;
+
+    # Get type.
+    my ($typeflag) = substr ($header, 156, 1);
+    return "not a regular file" if $typeflag ne '0' && $typeflag ne "\0";
+
+    # Get size.
+    my ($size) = oct (unpack ("Z*", substr ($header, 124, 12)));
+    return "bad size $size\n" if $size < 0;
+
+    # Copy file data.
+    my ($get_handle);
+    sysopen ($get_handle, $get_file_name, O_WRONLY | O_CREAT, 0666)
+      or die "$get_file_name: create: $!\n";
+    copy_file ($disk_handle, $disk_file_name, $get_handle, $get_file_name,
+	       $size);
+    close ($get_handle);
+
+    # Skip forward in disk up to beginning of next sector.
+    read_fully ($disk_handle, $disk_file_name, 512 - $size % 512)
+      if $size % 512;
+
+    return 0;
+}
+
+# Running simulators.
+
+# Runs the selected simulator.
+sub run_vm {
+    if ($sim eq 'bochs') {
+	run_bochs ();
+    } elsif ($sim eq 'qemu') {
+	run_qemu ();
+    } elsif ($sim eq 'player') {
+	run_player ();
+    } else {
+	die "unknown simulator `$sim'\n";
+    }
+}
+
+# Runs Bochs.
+sub run_bochs {
+    # Select Bochs binary based on the chosen debugger.
+    my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs';
+
+    my ($squish_pty);
+    if ($serial) {
+	$squish_pty = find_in_path ("squish-pty");
+	print "warning: can't find squish-pty, so terminal input will fail\n"
+	  if !defined $squish_pty;
+    }
+
+    # Write bochsrc.txt configuration file.
+    open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n";
+    print BOCHSRC <<EOF;
+romimage: file=\$BXSHARE/BIOS-bochs-latest
+vgaromimage: file=\$BXSHARE/VGABIOS-lgpl-latest
+boot: disk
+cpu: ips=1000000
+megs: $mem
+log: bochsout.txt
+panic: action=fatal
+# For older bochs:
+#user_shortcut: keys=ctrlaltdel
+# For more recent bochs:
+keyboard: user_shortcut=ctrl-alt-del
+EOF
+    print BOCHSRC "gdbstub: enabled=1, port=$gdb_port\n" if $debug eq 'gdb';
+    print BOCHSRC "clock: sync=", $realtime ? 'realtime' : 'none',
+      ", time0=0\n";
+    print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n"
+      if @disks > 2;
+    print_bochs_disk_line ("ata0-master", $disks[0]);
+    print_bochs_disk_line ("ata0-slave", $disks[1]);
+    print_bochs_disk_line ("ata1-master", $disks[2]);
+    print_bochs_disk_line ("ata1-slave", $disks[3]);
+    if ($vga ne 'terminal') {
+	if ($serial) {
+	    my $mode = defined ($squish_pty) ? "term" : "file";
+	    print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n";
+	}
+	print BOCHSRC "display_library: nogui\n" if $vga eq 'none';
+    } else {
+	print BOCHSRC "display_library: term\n";
+    }
+    close (BOCHSRC);
+
+    # Compose Bochs command line.
+    my (@cmd) = ($bin, '-q');
+    unshift (@cmd, $squish_pty) if defined $squish_pty;
+    push (@cmd, '-j', $jitter) if defined $jitter;
+
+    # Run Bochs.
+    print join (' ', @cmd), "\n";
+    my ($exit) = xsystem (@cmd);
+    if (WIFEXITED ($exit)) {
+	# Bochs exited normally.
+	# Ignore the exit code; Bochs normally exits with status 1,
+	# which is weird.
+    } elsif (WIFSIGNALED ($exit)) {
+	die "Bochs died with signal ", WTERMSIG ($exit), "\n";
+    } else {
+	die "Bochs died: code $exit\n";
+    }
+}
+
+sub print_bochs_disk_line {
+    my ($device, $disk) = @_;
+    if (defined $disk) {
+	my (%geom) = disk_geometry ($disk);
+	print BOCHSRC "$device: type=disk, path=$disk, mode=flat, ";
+	print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, ";
+	print BOCHSRC "translation=none\n";
+    }
+}
+
+# Runs QEMU.
+sub run_qemu {
+    print "warning: qemu doesn't support --terminal\n"
+      if $vga eq 'terminal';
+    print "warning: qemu doesn't support jitter\n"
+      if defined $jitter;
+    my (@cmd) = ('qemu-system-i386');
+    push (@cmd, '-device', 'isa-debug-exit');
+
+    my ($i);
+    for ($i = 0; $i < 4; $i++) {
+	if (defined $disks[$i]) {
+	    push (@cmd, '-drive');
+	    push (@cmd, "file=$disks[$i],format=raw,index=$i,media=disk");
+	}
+    }
+#    push (@cmd, '-hda', $disks[0]) if defined $disks[0];
+#    push (@cmd, '-hdb', $disks[1]) if defined $disks[1];
+#    push (@cmd, '-hdc', $disks[2]) if defined $disks[2];
+#    push (@cmd, '-hdd', $disks[3]) if defined $disks[3];
+    push (@cmd, '-m', $mem);
+    push (@cmd, '-net', 'none');
+    push (@cmd, '-nographic') if $vga eq 'none';
+    push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none';
+    push (@cmd, '-S') if $debug eq 'monitor';
+    push (@cmd, '-gdb', "tcp::$gdb_port", '-S') if $debug eq 'gdb';
+    push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none';
+    run_command (@cmd);
+}
+
+# player_unsup($flag)
+#
+# Prints a message that $flag is unsupported by VMware Player.
+sub player_unsup {
+    my ($flag) = @_;
+    print "warning: no support for $flag with VMware Player\n";
+}
+
+# Runs VMware Player.
+sub run_player {
+    player_unsup ("--$debug") if $debug ne 'none';
+    player_unsup ("--no-vga") if $vga eq 'none';
+    player_unsup ("--terminal") if $vga eq 'terminal';
+    player_unsup ("--jitter") if defined $jitter;
+    player_unsup ("--timeout"), undef $timeout if defined $timeout;
+    player_unsup ("--kill-on-failure"), undef $kill_on_failure
+      if defined $kill_on_failure;
+
+    $mem = round_up ($mem, 4);	# Memory must be multiple of 4 MB.
+
+    open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n";
+    chmod 0777 & ~umask, "pintos.vmx";
+    print VMX <<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 ($dsk) = $disks[$i];
+	last 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 ($dsk);
+	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.
+
+sub extend_file {
+    my ($handle, $file_name, $size) = @_;
+    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 ($file) = @_;
+    my ($size) = -s $file;
+    die "$file: stat: $!\n" if !defined $size;
+    die "$file: size $size not a multiple of 512 bytes\n" if $size % 512;
+    my ($cyl_size) = 512 * 16 * 63;
+    my ($cylinders) = ceil ($size / $cyl_size);
+
+    return (CAPACITY => $size / 512,
+	    C => $cylinders,
+	    H => 16,
+	    S => 63);
+}
+
+# Subprocess utilities.
+
+# run_command(@args)
+#
+# Runs xsystem(@args).
+# Also prints the command it's running and checks that it succeeded.
+sub run_command {
+    print join (' ', @_), "\n";
+    die "command failed\n" if xsystem (@_);
+}
+
+# xsystem(@args)
+#
+# Creates a subprocess via exec(@args) and waits for it to complete.
+# Relays common signals to the subprocess.
+# If $timeout is set then the subprocess will be killed after that long.
+sub xsystem {
+    # QEMU turns off local echo and does not restore it if killed by a signal.
+    # We compensate by restoring it ourselves.
+    my $cleanup = sub {};
+    if (isatty (0)) {
+	my $termios = POSIX::Termios->new;
+	$termios->getattr (0);
+	$cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); }
+    }
+
+    # Create pipe for filtering output.
+    pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure;
+
+    my ($pid) = fork;
+    if (!defined ($pid)) {
+	# Fork failed.
+	die "fork: $!\n";
+    } elsif (!$pid) {
+	# Running in child process.
+	dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n"
+	  if $kill_on_failure;
+	exec_setitimer (@_);
+    } else {
+	# Running in parent process.
+	close $out if $kill_on_failure;
+
+	my ($cause);
+	local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); };
+	local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); };
+	local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); };
+	alarm ($timeout * get_load_average () + 1) if defined ($timeout);
+
+	if ($kill_on_failure) {
+	    # Filter output.
+	    my ($buf) = "";
+	    my ($boots) = 0;
+	    local ($|) = 1;
+	    for (;;) {
+		if (waitpid ($pid, WNOHANG) != 0) {
+		    # Subprocess died.  Pass through any remaining data.
+		    do { print $buf } while sysread ($in, $buf, 4096) > 0;
+		    last;
+		}
+
+		# Read and print out pipe data.
+		my ($len) = length ($buf);
+		my ($n_read) = sysread ($in, $buf, 4096, $len);
+		waitpid ($pid, 0), last if !defined ($n_read) || $n_read <= 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_number ()) {
+	    seek (STDOUT, 0, 2);
+	    print "\nTIMEOUT after $timeout seconds of host CPU time\n";
+	    exit 0;
+	}
+
+        # Kind of a gross hack, because qemu's isa-debug-exit device
+        # only allows odd-numbered exit values, so we can't exit
+        # cleanly with 0.  We use exit status 0x63 as an alternate
+        # "clean" exit status.
+	return ($? != 0x6300) && $?;
+    }
+}
+
+# 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 ($^V 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_number {
+    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/utils/pintos-gdb b/utils/pintos-gdb
new file mode 100644
index 0000000000000000000000000000000000000000..1d9ec87ef97d859b39e7f9c17375cc0e58677ba6
--- /dev/null
+++ b/utils/pintos-gdb
@@ -0,0 +1,20 @@
+#! /bin/sh
+
+# Path to GDB macros file.  Customize for your site.
+GDBMACROS=$(dirname ${0})/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/utils/pintos-mkdisk b/utils/pintos-mkdisk
new file mode 100644
index 0000000000000000000000000000000000000000..87b1563e69c517070e81ad32c36adfabdf6df2a5
--- /dev/null
+++ b/utils/pintos-mkdisk
@@ -0,0 +1,134 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX;
+use Getopt::Long qw(:config bundling);
+use Fcntl 'SEEK_SET';
+
+# Read Pintos.pm from the same directory as this program.
+BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
+
+our ($disk_fn);			# Output disk file name.
+our (%parts);			# Partitions.
+our ($format);			# "partitioned" (default) or "raw"
+our (%geometry);		# IDE disk geometry.
+our ($align);			# Align partitions on cylinders?
+our ($loader_fn);		# File name of loader.
+our ($include_loader);		# Include loader?
+our (@kernel_args);		# Kernel arguments.
+
+if (grep ($_ eq '--', @ARGV)) {
+    @kernel_args = @ARGV;
+    @ARGV = ();
+    while ((my $arg = shift (@kernel_args)) ne '--') {
+	push (@ARGV, $arg);
+    }
+}
+
+GetOptions ("h|help" => sub { usage (0); },
+
+	    "kernel=s" => \&set_part,
+	    "filesys=s" => \&set_part,
+	    "scratch=s" => \&set_part,
+	    "swap=s" => \&set_part,
+
+	    "filesys-size=s" => \&set_part,
+	    "scratch-size=s" => \&set_part,
+	    "swap-size=s" => \&set_part,
+
+	    "kernel-from=s" => \&set_part,
+	    "filesys-from=s" => \&set_part,
+	    "scratch-from=s" => \&set_part,
+	    "swap-from=s" => \&set_part,
+
+	    "format=s" => \$format,
+	    "loader:s" => \&set_loader,
+	    "no-loader" => \&set_no_loader,
+	    "geometry=s" => \&set_geometry,
+	    "align=s" => \&set_align)
+  or exit 1;
+usage (1) if @ARGV != 1;
+
+$disk_fn = $ARGV[0];
+die "$disk_fn: already exists\n" if -e $disk_fn;
+
+# Sets the loader to copy to the MBR.
+sub set_loader {
+    die "can't specify both --loader and --no-loader\n"
+      if defined ($include_loader) && !$include_loader;
+    $include_loader = 1;
+    $loader_fn = $_[1] if $_[1] ne '';
+}
+
+# Disables copying a loader to the MBR.
+sub set_no_loader {
+    die "can't specify both --loader and --no-loader\n"
+      if defined ($include_loader) && $include_loader;
+    $include_loader = 0;
+}
+
+# Figure out whether to include a loader.
+$include_loader = exists ($parts{KERNEL}) && $format eq 'partitioned'
+  if !defined ($include_loader);
+die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw';
+die "can't write command-line arguments without --loader or --kernel\n"
+  if @kernel_args && !$include_loader;
+print STDERR "warning: --loader only makes sense without --kernel "
+  . "if this disk will be used to load a kernel from another disk\n"
+  if $include_loader && !exists ($parts{KERNEL});
+
+# Open disk.
+my ($disk_handle);
+open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n";
+
+# Read loader.
+my ($loader);
+$loader = read_loader ($loader_fn) if $include_loader;
+
+# Write disk.
+my (%disk) = %parts;
+$disk{DISK} = $disk_fn;
+$disk{HANDLE} = $disk_handle;
+$disk{ALIGN} = $align;
+$disk{GEOMETRY} = %geometry;
+$disk{FORMAT} = $format;
+$disk{LOADER} = $loader;
+$disk{ARGS} = \@kernel_args;
+assemble_disk (%disk);
+
+# Done.
+exit 0;
+
+sub usage {
+    print <<'EOF';
+pintos-mkdisk, a utility for creating Pintos virtual disks
+Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...]
+where DISK is the virtual disk to create,
+      each ARGUMENT is inserted into the command line written to DISK,
+  and each OPTION is one of the following options.
+Partition options: (where PARTITION is one of: kernel filesys scratch swap)
+  --PARTITION=FILE         Use a copy of FILE for the given PARTITION
+  --PARTITION-size=SIZE    Create an empty PARTITION of the given SIZE in MB
+  --PARTITION-from=DISK    Use of a copy of the given PARTITION in DISK
+  (There is no --kernel-size option.)
+Output disk options:
+  --format=partitioned     Write partition table to output (default)
+  --format=raw             Do not write partition table to output
+  (Pintos can only use partitioned disks.)
+Partitioned format output options:
+  --loader[=FILE]          Get bootstrap loader from FILE (default: loader.bin
+                           if --kernel option is specified, empty otherwise)
+  --no-loader              Do not include a bootstrap loader
+  --geometry=H,S           Use H head, S sector geometry (default: 16, 63)
+  --geometry=zip           Use 64 head, 32 sector geometry for USB-ZIP boot
+                           per http://syslinux.zytor.com/usbkey.php
+  --align=bochs            Round size to cylinder for Bochs support (default)
+  --align=full             Align partition boundaries to cylinder boundary to
+                           let fdisk guess correct geometry and quiet warnings
+  --align=none             Don't align partitions at all, to save space
+Other options:
+  -h, --help               Display this help message.
+EOF
+    exit ($_[0]);
+}
diff --git a/utils/pintos-set-cmdline b/utils/pintos-set-cmdline
new file mode 100644
index 0000000000000000000000000000000000000000..8c8f702435ba718786f662b3ceda5b2a1bd7519e
--- /dev/null
+++ b/utils/pintos-set-cmdline
@@ -0,0 +1,42 @@
+#! /usr/bin/perl -w
+
+use strict;
+use Fcntl 'SEEK_SET';
+
+# Read Pintos.pm from the same directory as this program.
+BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
+
+# Get command-line arguments.
+usage (0) if @ARGV == 1 && $ARGV[0] eq '--help';
+usage (1) if @ARGV < 2 || $ARGV[1] ne '--';
+my ($disk, undef, @kernel_args) = @ARGV;
+
+# Open disk.
+my ($handle);
+open ($handle, '+<', $disk) or die "$disk: open: $!\n";
+
+# Check that it's a partitioned disk with a Pintos loader.
+my ($buffer) = read_fully ($handle, $disk, 512);
+unpack ("x510 v", $buffer) == 0xaa55 or die "$disk: not a partitioned disk\n";
+$buffer =~ /Pintos/ or die "$disk: does not contain Pintos loader\n";
+
+# Write the command line.
+our ($LOADER_SIZE);
+sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE
+  or die "$disk: seek: $!\n";
+write_fully ($handle, $disk, make_kernel_command_line (@kernel_args));
+
+# Close disk.
+close ($handle) or die "$disk: close: $!\n";
+
+exit 0;
+
+sub usage {
+    print <<'EOF';
+pintos-set-cmdline, a utility for changing the command line in Pintos disks
+Usage: pintos-set-cmdline DISK -- [ARGUMENT...]
+where DISK is a bootable disk containing a Pintos loader
+  and each ARGUMENT is inserted into the command line written to DISK.
+EOF
+    exit ($_[0]);
+}
diff --git a/utils/setitimer-helper.c b/utils/setitimer-helper.c
new file mode 100644
index 0000000000000000000000000000000000000000..0a9625adb944bd000da3d64339ee95bd436112f2
--- /dev/null
+++ b/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/utils/squish-pty.c b/utils/squish-pty.c
new file mode 100644
index 0000000000000000000000000000000000000000..aa9b41ba70e0c192b6733baaca69eda6d6a81487
--- /dev/null
+++ b/utils/squish-pty.c
@@ -0,0 +1,318 @@
+#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.
+	Sets *FD to -1 if the fd is no longer readable or writable. */
+static void 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. */
+				*fd = -1;
+			}
+			else
+				fail_io(call);
+		}
+	}
+	else {
+		if (retval == 0) {
+			close(*fd);
+			*fd = -1;
+		}
+		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[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))
+			break;
+
+		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
+					handle_error(n, &p->in, p->in == pty, "read");
+			}
+			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
+					handle_error(n, &p->out, p->out == pty, "write");
+			}
+		}
+	}
+
+	if (pipes[1].out == -1)
+		return;
+
+	make_nonblocking(STDOUT_FILENO, false);
+	for (;;) {
+		struct pipe* p = &pipes[1];
+		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;
+	}
+}
+
+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/utils/squish-unix.c b/utils/squish-unix.c
new file mode 100644
index 0000000000000000000000000000000000000000..563704317ffd383e5bd773148c878875a00a1c78
--- /dev/null
+++ b/utils/squish-unix.c
@@ -0,0 +1,310 @@
+#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/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.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 socket if
+	FD_IS_SOCK is true, that returned end-of-file or error
+	indication RETVAL.  The system call is named CALL, for use in
+	error messages.  Returns true if processing may continue,
+	false if we're all done. */
+static bool handle_error(ssize_t retval, int* fd, bool fd_is_sock, const char* call)
+{
+	if (retval == 0) {
+		if (fd_is_sock)
+			return false;
+		else {
+			*fd = -1;
+			return true;
+		}
+	}
+	else
+		fail_io(call);
+}
+
+/* Copies data from stdin to SOCK and from SOCK to stdout until no
+	more data can be read or written. */
+static void relay(int sock)
+{
+	struct pipe {
+		int in, out;
+		char buf[BUFSIZ];
+		size_t size, ofs;
+		bool active;
+	};
+	struct pipe pipes[2];
+
+	/* In case stdin is a file, go back to the beginning.
+		This allows replaying the input on reset. */
+	lseek(STDIN_FILENO, 0, SEEK_SET);
+
+	/* Make SOCK, stdin, and stdout non-blocking. */
+	make_nonblocking(sock, true);
+	make_nonblocking(STDIN_FILENO, true);
+	make_nonblocking(STDOUT_FILENO, true);
+
+	/* Configure noncanonical mode on stdin to avoid waiting for
+		end-of-line. */
+	make_noncanon(STDIN_FILENO, 1, 0);
+
+	memset(pipes, 0, sizeof pipes);
+	pipes[0].in = STDIN_FILENO;
+	pipes[0].out = sock;
+	pipes[1].in = sock;
+	pipes[1].out = STDOUT_FILENO;
+
+	while (pipes[0].in != -1 || pipes[1].in != -1
+			 || (pipes[1].size && pipes[1].out != -1)) {
+		fd_set read_fds, write_fds;
+		sigset_t empty_set;
+		int retval;
+		int i;
+
+		FD_ZERO(&read_fds);
+		FD_ZERO(&write_fds);
+		for (i = 0; i < 2; i++) {
+			struct pipe* p = &pipes[i];
+
+			/* Don't do anything with the stdin->sock pipe until we
+				have some data for the sock->stdout pipe.  If we get
+				too eager, vmplayer will throw away our input. */
+			if (i == 0 && !pipes[1].active)
+				continue;
+
+			if (p->in != -1 && p->size + p->ofs < sizeof p->buf)
+				FD_SET(p->in, &read_fds);
+			if (p->out != -1 && p->size > 0)
+				FD_SET(p->out, &write_fds);
+		}
+		sigemptyset(&empty_set);
+		retval = pselect(FD_SETSIZE, &read_fds, &write_fds, NULL, NULL, &empty_set);
+		if (retval < 0) {
+			if (errno == EINTR) {
+				/* Child died.  Do final relaying. */
+				struct pipe* p = &pipes[1];
+				if (p->out == -1)
+					exit(0);
+				make_nonblocking(STDOUT_FILENO, false);
+				for (;;) {
+					ssize_t n;
+
+					/* Write buffer. */
+					while (p->size > 0) {
+						n = write(p->out, p->buf + p->ofs, p->size);
+						if (n < 0)
+							fail_io("write");
+						else if (n == 0)
+							fail_io("zero-length write");
+						p->ofs += n;
+						p->size -= n;
+					}
+					p->ofs = 0;
+
+					p->size = n = read(p->in, p->buf, sizeof p->buf);
+					if (n <= 0)
+						exit(0);
+				}
+			}
+			fail_io("select");
+		}
+
+		for (i = 0; i < 2; i++) {
+			struct pipe* p = &pipes[i];
+			if (p->in != -1 && FD_ISSET(p->in, &read_fds)) {
+				ssize_t n = read(
+					 p->in, p->buf + p->ofs + p->size, sizeof p->buf - p->ofs - p->size);
+				if (n > 0) {
+					p->active = true;
+					p->size += n;
+					if (p->size == BUFSIZ && p->ofs != 0) {
+						memmove(p->buf, p->buf + p->ofs, p->size);
+						p->ofs = 0;
+					}
+				}
+				else if (!handle_error(n, &p->in, p->in == sock, "read"))
+					return;
+			}
+			if (p->out != -1 && FD_ISSET(p->out, &write_fds)) {
+				ssize_t n = write(p->out, p->buf + p->ofs, p->size);
+				if (n > 0) {
+					p->ofs += n;
+					p->size -= n;
+					if (p->size == 0)
+						p->ofs = 0;
+				}
+				else if (!handle_error(n, &p->out, p->out == sock, "write"))
+					return;
+			}
+		}
+	}
+}
+
+static void sigchld_handler(int signo __attribute__((unused)))
+{
+	/* Nothing to do. */
+}
+
+int main(int argc __attribute__((unused)), char* argv[])
+{
+	pid_t pid;
+	struct itimerval zero_itimerval;
+	struct sockaddr_un sun;
+	sigset_t sigchld_set;
+	int sock;
+
+	if (argc < 3) {
+		fprintf(
+			 stderr,
+			 "usage: squish-unix SOCKET COMMAND [ARG]...\n"
+			 "Squishes both stdin and stdout into a single Unix domain\n"
+			 "socket named SOCKET, and runs COMMAND as a subprocess.\n");
+		return EXIT_FAILURE;
+	}
+
+	/* Create socket. */
+	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
+	if (sock < 0)
+		fail_io("socket");
+
+	/* Configure socket. */
+	sun.sun_family = AF_LOCAL;
+	strncpy(sun.sun_path, argv[1], sizeof sun.sun_path);
+	sun.sun_path[sizeof sun.sun_path - 1] = '\0';
+	if (unlink(sun.sun_path) < 0 && errno != ENOENT)
+		fail_io("unlink");
+	if (bind(
+			  sock,
+			  (struct sockaddr*) &sun,
+			  (offsetof(struct sockaddr_un, sun_path) + strlen(sun.sun_path) + 1))
+		 < 0)
+		fail_io("bind");
+
+	/* Listen on socket. */
+	if (listen(sock, 1) < 0)
+		fail_io("listen");
+
+	/* Block SIGCHLD and set up a handler for it. */
+	sigemptyset(&sigchld_set);
+	sigaddset(&sigchld_set, SIGCHLD);
+	if (sigprocmask(SIG_BLOCK, &sigchld_set, NULL) < 0)
+		fail_io("sigprocmask");
+	if (signal(SIGCHLD, sigchld_handler) == SIG_ERR)
+		fail_io("signal");
+
+	/* Save the virtual interval timer, which might have been set
+		by the process that ran us.  It really should be applied to
+		our child process. */
+	memset(&zero_itimerval, 0, sizeof zero_itimerval);
+	if (setitimer(ITIMER_VIRTUAL, &zero_itimerval, NULL) < 0)
+		fail_io("setitimer");
+
+	pid = fork();
+	if (pid < 0)
+		fail_io("fork");
+	else if (pid != 0) {
+		/* Running in parent process. */
+		make_nonblocking(sock, true);
+		for (;;) {
+			fd_set read_fds;
+			sigset_t empty_set;
+			int retval;
+			int conn;
+
+			/* Wait for connection. */
+			FD_ZERO(&read_fds);
+			FD_SET(sock, &read_fds);
+			sigemptyset(&empty_set);
+			retval = pselect(sock + 1, &read_fds, NULL, NULL, NULL, &empty_set);
+			if (retval < 0) {
+				if (errno == EINTR)
+					break;
+				fail_io("select");
+			}
+
+			/* Accept connection. */
+			conn = accept(sock, NULL, NULL);
+			if (conn < 0)
+				fail_io("accept");
+
+			/* Relay connection. */
+			relay(conn);
+			close(conn);
+		}
+		return 0;
+	}
+	else {
+		/* Running in child process. */
+		if (close(sock) < 0)
+			fail_io("close");
+		execvp(argv[2], argv + 2);
+		fail_io("exec");
+	}
+}
diff --git a/vm/.gitignore b/vm/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6d5357c015ab6f0b7ee7074381afdd3da82e06eb
--- /dev/null
+++ b/vm/.gitignore
@@ -0,0 +1,3 @@
+build
+bochsrc.txt
+bochsout.txt
diff --git a/vm/Make.vars b/vm/Make.vars
new file mode 100644
index 0000000000000000000000000000000000000000..e3c33a755b4ffcfc5236ddea4cb6524630712bae
--- /dev/null
+++ b/vm/Make.vars
@@ -0,0 +1,7 @@
+# -*- makefile -*-
+
+kernel.bin: DEFINES = -DUSERPROG -DFILESYS -DVM
+KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm
+TEST_SUBDIRS = tests/userprog tests/vm tests/filesys/base
+GRADING_FILE = $(SRCDIR)/tests/vm/Grading
+SIMULATOR = --qemu
diff --git a/vm/Makefile b/vm/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..34c10aa4f508714da040e81389fa51e56ba2d97a
--- /dev/null
+++ b/vm/Makefile
@@ -0,0 +1 @@
+include ../Makefile.kernel