diff --git a/Makefile b/Makefile
index e12ddf1aec0ccc04a020458599f0ec380d72ca5a..31bc94638dc34c1880864ca3bdd026adac602a38 100644
--- a/Makefile
+++ b/Makefile
@@ -72,9 +72,9 @@ vectors.S : vectors.pl
 
 ULIB = ulib.o usys.o printf.o umalloc.o
 
-usertests : usertests.o $(ULIB)
-	$(LD) -N -e main -Ttext 0 -o usertests usertests.o $(ULIB)
-	$(OBJDUMP) -S usertests > usertests.asm
+_usertests : usertests.o $(ULIB)
+	$(LD) -N -e main -Ttext 0 -o _usertests usertests.o $(ULIB)
+	$(OBJDUMP) -S _usertests > usertests.asm
 
 _echo : echo.o $(ULIB)
 	$(LD) -N -e main -Ttext 0 -o _echo echo.o $(ULIB)
@@ -117,10 +117,16 @@ _zombie: zombie.o $(ULIB)
 	$(LD) -N -e main -Ttext 0 -o _zombie zombie.o $(ULIB)
 	$(OBJDUMP) -S _zombie > zombie.asm
 
+_forktest: forktest.o $(ULIB)
+	# forktest has less library code linked in - needs to be small
+	# in order to be able to max out the proc table.
+	$(LD) -N -e main -Ttext 0 -o _forktest forktest.o ulib.o usys.o
+	$(OBJDUMP) -S _forktest > forktest.asm
+
 mkfs : mkfs.c fs.h
 	cc -o mkfs mkfs.c
 
-UPROGS=usertests _echo _cat _init _kill _ln _ls _mkdir _rm _sh _zombie
+UPROGS=_usertests _echo _cat _init _kill _ln _ls _mkdir _rm _sh _zombie _forktest
 fs.img : mkfs README $(UPROGS)
 	./mkfs fs.img README $(UPROGS)
 
diff --git a/forktest.c b/forktest.c
new file mode 100644
index 0000000000000000000000000000000000000000..90cc38c694e83ac7b76cbc5e9b8b15bd68737cab
--- /dev/null
+++ b/forktest.c
@@ -0,0 +1,54 @@
+// Test that fork fails gracefully.
+// Tiny executable so that the limit can be filling the proc table.
+
+#include "types.h"
+#include "stat.h"
+#include "user.h"
+
+void
+printf(int fd, char *s, ...)
+{
+  write(fd, s, strlen(s));
+}
+
+void
+forktest(void)
+{
+  int n, pid;
+
+  printf(1, "fork test\n");
+
+  for(n=0; n<1000; n++){
+    pid = fork();
+    if(pid < 0)
+      break;
+    if(pid == 0)
+      exit();
+  }
+  
+  if(n == 1000){
+    printf(1, "fork claimed to work 1000 times!\n");
+    exit();
+  }
+  
+  for(; n > 0; n--){
+    if(wait() < 0){
+      printf(1, "wait stopped early\n");
+      exit();
+    }
+  }
+  
+  if(wait() != -1){
+    printf(1, "wait got too many\n");
+    exit();
+  }
+  
+  printf(1, "fork test OK\n");
+}
+
+int
+main(void)
+{
+  forktest();
+  exit();
+}
diff --git a/usertests.c b/usertests.c
index 3a9cd9aad9e9f9af299682904be08a29a4efe111..17c70445818b359d3e2ab5430977d5f6c3ff596d 100644
--- a/usertests.c
+++ b/usertests.c
@@ -1189,6 +1189,44 @@ iref(void)
   printf(1, "empty file name OK\n");
 }
 
+// test that fork fails gracefully
+// the forktest binary also does this, but it runs out of proc entries first.
+// inside the bigger usertests binary, we run out of memory first.
+void
+forktest(void)
+{
+  int n, pid;
+
+  printf(1, "fork test\n");
+
+  for(n=0; n<1000; n++){
+    pid = fork();
+    if(pid < 0)
+      break;
+    if(pid == 0)
+      exit();
+  }
+  
+  if(n == 1000){
+    printf(1, "fork claimed to work 1000 times!\n");
+    exit();
+  }
+  
+  for(; n > 0; n--){
+    if(wait() < 0){
+      printf(1, "wait stopped early\n");
+      exit();
+    }
+  }
+  
+  if(wait() != -1){
+    printf(1, "wait got too many\n");
+    exit();
+  }
+  
+  printf(1, "fork test OK\n");
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -1223,6 +1261,7 @@ main(int argc, char *argv[])
   sharedfd();
   dirfile();
   iref();
+  forktest();
 
   exectest();