diff --git a/Makefile b/Makefile
index a476cf4179a27c449f5a31bfcae2c9c27d09cf36..9a6b02d1732fadad3d7427eae4c3aafd51cb3a53 100644
--- a/Makefile
+++ b/Makefile
@@ -67,6 +67,10 @@ usertests : usertests.o $(ULIB)
 	$(LD) -N -e main -Ttext 0 -o usertests usertests.o $(ULIB)
 	$(OBJDUMP) -S usertests > usertests.asm
 
+fstests : fstests.o $(ULIB)
+	$(LD) -N -e main -Ttext 0 -o fstests fstests.o $(ULIB)
+	$(OBJDUMP) -S fstests > fstests.asm
+
 echo : echo.o $(ULIB)
 	$(LD) -N -e main -Ttext 0 -o echo echo.o $(ULIB)
 	$(OBJDUMP) -S echo > echo.asm
@@ -102,12 +106,12 @@ rm : rm.o $(ULIB)
 mkfs : mkfs.c fs.h
 	cc -o mkfs mkfs.c
 
-fs.img : mkfs userfs usertests echo cat readme init sh ls mkdir rm
-	./mkfs fs.img userfs usertests echo cat readme init sh ls mkdir rm
+fs.img : mkfs userfs usertests echo cat readme init sh ls mkdir rm fstests
+	./mkfs fs.img userfs usertests echo cat readme init sh ls mkdir rm fstests
 
 -include *.d
 
 clean : 
 	rm -f *.o *.d *.asm vectors.S parport.out \
 		bootblock kernel xv6.img user1 userfs usertests \
-		fs.img mkfs echo init
+		fs.img mkfs echo init fstests
diff --git a/Notes b/Notes
index ab28ec312f70afb86f193a0f329171828ab6445b..dd81f1b9c9c42a226ffb15bb81a9076ebd88fa73 100644
--- a/Notes
+++ b/Notes
@@ -22,32 +22,14 @@ no kernel malloc(), just kalloc() for user core
 
 user pointers aren't valid in the kernel
 
-setting up first process
-  we do want a process zero, as template
-    but not runnable
-  just set up return-from-trap frame on new kernel stack
-  fake user program that calls exec
-
-map text read-only?
-shared text?
-
-what's on the stack during a trap or sys call?
-  PUSHA before scheduler switch? for callee-saved registers.
-  segment contents?
-  what does iret need to get out of the kernel?
-  how does INT know what kernel stack to use?
- 
-are interrupts turned on in the kernel? probably.
-
-per-cpu curproc
-one tss per process, or one per cpu?
-one segment array per cpu, or per process?
+are interrupts turned on in the kernel? yes.
 
 pass curproc explicitly, or implicit from cpu #?
   e.g. argument to newproc()?
   hmm, you need a global curproc[cpu] for trap() &c
 
-test stack expansion
+no stack expansion
+
 test running out of memory, process slots
 
 we can't really use a separate stack segment, since stack addresses
@@ -56,16 +38,6 @@ data vs text. how can we have a gap between data and stack, so that
 both can grow, without committing 4GB of physical memory? does this
 mean we need paging?
 
-what's the simplest way to add the paging we need?
-  one page table, re-write it each time we leave the kernel?
-  page table per process?
-  probably need to use 0-0xffffffff segments, so that
-    both data and stack pointers always work
-  so is it now worth it to make a process's phys mem contiguous?
-  or could use segment limits and 4 meg pages?
-    but limits would prevent using stack pointers as data pointers
-  how to write-protect text? not important?
-
 perhaps have fixed-size stack, put it in the data segment?
 
 oops, if kernel stack is in contiguous user phys mem, then moving
@@ -87,19 +59,6 @@ test children being inherited by grandparent &c
 
 some sleep()s should be interruptible by kill()
 
-cli/sti in acquire/release should nest!
-  in case you acquire two locks
-
-what would need fixing if we got rid of kernel_lock?
-  console output
-  proc_exit() needs lock on proc *array* to deallocate
-  kill() needs lock on proc *array*
-  allocator's free list
-  global fd table (really free-ness)
-  sys_close() on fd table
-  fork on proc list, also next pid
-    hold lock until public slots in proc struct initialized
-
 locks
   init_lock
     sequences CPU startup
@@ -110,37 +69,17 @@ locks
   memory allocator
   printf
 
-wakeup needs proc_table_lock
-  so we need recursive locks?
-  or you must hold the lock to call wakeup?
-
 in general, the table locks protect both free-ness and
   public variables of table elements
   in many cases you can use table elements w/o a lock
   e.g. if you are the process, or you are using an fd
 
-lock code shouldn't call cprintf...
-
-nasty hack to allow locks before first process,
-  and to allow them in interrupts when curproc may be zero
-
-race between release and sleep in sys_wait()
-race between sys_exit waking up parent and setting state=ZOMBIE
-race in pipe code when full/empty
-
 lock order
   per-pipe lock
   proc_table_lock fd_table_lock kalloc_lock
   console_lock
 
-condition variable + mutex that protects it
-  proc * (for wait()), proc_table_lock
-  pipe structure, pipe lock
-
-systematic way to test sleep races?
-  print something at the start of sleep?
-
-do you have to be holding the mutex in order to call wakeup()?
+do you have to be holding the mutex in order to call wakeup()? yes
 
 device interrupts don't clear FL_IF
   so a recursive timer interrupt is possible
@@ -156,202 +95,11 @@ inode->count counts in-memory pointers to the struct
 blocks and inodes have ad-hoc sleep-locks
   provide a single mechanism?
 
-need to lock bufs in bio between bread and brelse
-
 test 14-character file names
 and file arguments longer than 14
-and directories longer than one sector
 
 kalloc() can return 0; do callers handle this right?
 
-why directing interrupts to cpu 1 causes trouble
-  cpu 1 turns on interrupts with no tss!
-    and perhaps a stale gdt (from boot)
-  since it has never run a process, never called setupsegs()
-  but does cpu really need the tss?
-    not switching stacks
-  fake process per cpu, just for tss?
-    seems like a waste
-  move tss to cpu[]?
-    but tss points to per-process kernel stack
-    would also give us a gdt
-  OOPS that wasn't the problem
-
-wait for other cpu to finish starting before enabling interrupts?
-  some kind of crash in ide_init ioapic_enable cprintf
-move ide_init before mp_start?
-  didn't do any good
-  maybe cpu0 taking ide interrupt, cpu1 getting a nested lock error
-
-cprintfs are screwed up if locking is off
-  often loops forever
-  hah, just use lpt alone
-
-looks like cpu0 took the ide interrupt and was the last to hold
-the lock, but cpu1 thinks it is nested
-cpu0 is in load_icode / printf / cons_putc
-  probably b/c cpu1 cleared use_console_lock
-cpu1 is in scheduler() / printf / acquire
-
-  1: init timer
-  0: init timer
-  cpu 1 initial nlock 1
-  ne0s:t iidd el_occnkt rc
-  onsole cpu 1 old caller stack 1001A5 10071D 104DFF 1049FE
-  panic: acquire
-  ^CNext at t=33002418
-  (0) [0x00100091] 0008:0x00100091 (unk. ctxt): jmp .+0xfffffffe          ; ebfe
-  (1) [0x00100332] 0008:0x00100332 (unk. ctxt): jmp .+0xfffffffe          
-  
-why is output interleaved even before panic?
-
-does release turn on interrupts even inside an interrupt handler?
-
-overflowing cpu[] stack?
-  probably not, change from 512 to 4096 didn't do anything
-
-
-  1: init timer
-  0: init timer
-  cnpeus te11  linnitki aclo nnoolleek  cp1u
-   ss  oarltd  sccahleldeul esrt aocnk  cpu 0111 Ej6  buf1 01A3140 C5118 
-  0
-  la anic1::7 0a0c0  uuirr e
-  ^CNext at t=31691050
-  (0) [0x00100373] 0008:0x00100373 (unk. ctxt): jmp .+0xfffffffe          ; ebfe
-  (1) [0x00100091] 0008:0x00100091 (unk. ctxt): jmp .+0xfffffffe          ; ebfe
-
-cpu0:
-
-0: init timer
-nested lock console cpu 0 old caller stack 1001e6 101a34 1 0
-  (that's mpmain)
-panic: acquire
-
-cpu1:
-
-1: init timer
-cpu 1 initial nlock 1
-start scheduler on cpu 1 jmpbuf ...
-la 107000 lr ...
-  that is, nlock != 0
-
-maybe a race; acquire does
-  locked = 1
-  cpu = cpu()
-what if another acquire calls holding w/ locked = 1 but
-  before cpu is set?
-
-if I type a lot (kbd), i get a panic
-cpu1 in scheduler: panic "holding locks in scheduler"
-cpu0 also in the same panic!
-recursive interrupt?
-  FL_IF is probably set during interrupt... is that correct?
-again:
-  olding locks in scheduler
-  trap v 33 eip 100ED3 c (that is, interrupt while holding a lock)
-  100ed3 is in lapic_write
-again:
-  trap v 33 eip 102A3C cpu 1 nlock 1 (in acquire)
-  panic: interrupt while holding a lock
-again:
-  trap v 33 eip 102A3C cpu 1 nlock 1
-  panic: interrupt while holding a lock
-OR is it the cprintf("kbd overflow")?
-  no, get panic even w/o that cprintf
-OR a release() at interrupt time turns interrupts back on?
-  of course i don't think they were off...
-OK, fixing trap.c to make interrupts turn off FL_IF
-  that makes it take longer, but still panics
-  (maybe b/c release sets FL_IF)
-
-shouldn't something (PIC?) prevent recursive interrupts of same IRQ?
-  or should FL_IF be clear during all interrupts?
-
-maybe acquire should remember old FL_IF value, release should restore
-  if acquire did cli()
-
-DUH the increment of nlock in acquire() happens before the cli!
-  so the panic is probably not a real problem
-  test nlock, cli(), then increment?
-
-BUT now userfs doesn't do the final cat README
-
-AND w/ cprintf("kbd overflow"), panic holding locks in scheduler
-  maybe also simulataneous panic("interrupt while holding a lock")
-
-again (holding down x key):
-  kbd overflow
-  kbd oaaniicloowh
-  olding locks in scheduler
-  trap v 33 eip 100F5F c^CNext at t=32166285
-  (0) [0x0010033e] 0008:0010033e (unk. ctxt): jmp .+0xfffffffe (0x0010033e) ; ebfe
-  (1) [0x0010005c] 0008:0010005c (unk. ctxt): jmp .+0xfffffffe (0x0010005c) ; ebfe
-cpu0 paniced due to holding locks in scheduler
-cpu1 got panic("interrupt while holding a lock")
-  again in lapic_write.
-  while re-enabling an IRQ?
-
-again:
-cpu 0 panic("holding locks in scheduler")
-  but didn't trigger related panics earlier in scheduler or sched()
-  of course the panic is right after release() and thus sti()
-  so we may be seeing an interrupt that left locks held
-cpu 1 unknown panic
-why does it happen to both cpus at the same time?
-
-again:
-cpu 0 panic("holding locks in scheduler")
-  but trap() didn't see any held locks on return
-cpu 1 no apparent panic
-
-again:
-cpu 0 panic: holding too many locks in scheduler
-cpu 1 panic: kbd_intr returned while holding a lock
-
-again:
-cpu 0 panic: holding too man
-  la 10d70c lr 10027b
-  those don't seem to be locks...
-  only place non-constant lock is used is sleep()'s 2nd arg
-  maybe register not preserved across context switch?
-  it's in %esi...
-  sched() doesn't touch %esi
-  %esi is evidently callee-saved
-  something to do with interrupts? since ordinarily it works
-cpu 1 panic: kbd_int returned while holding a lock
-  la 107340 lr 107300
-  console_lock and kbd_lock
-
-maybe console_lock is often not released due to change
-  in use_console_lock (panic on other cpu)
-
-again:
-cpu 0: panic: h...
-  la 10D78C lr 102CA0
-cpu 1: panic: acquire FL_IF (later than cpu 0)
-
-but if sleep() were acquiring random locks, we'd see panics
-in release, after sleep() returned.
-actually when system is idle, maybe no-one sleeps at all.
-  just scheduler() and interrupts
-
-questions:
-  does userfs use pipes? or fork?
-    no
-  does anything bad happen if process 1 exits? eg exit() in cat.c
-    looks ok
-  are there really no processes left?
-  lock_init() so we can have a magic number?
-
-HMM maybe the variables at the end of struct cpu are being overwritten
-  nlocks, lastacquire, lastrelease
-  by cpu->stack?
-  adding junk buffers maybe causes crash to take longer...
-  when do we run on cpu stack?
-  just in scheduler()?
-    and interrupts from scheduler()
- 
 OH! recursive interrupts will use up any amount of cpu[].stack!
   underflow and wrecks *previous* cpu's struct
 
@@ -360,15 +108,26 @@ mkdir
 sh arguments
 sh redirection
 indirect blocks
-two bugs in unlink: don't just return if nlink > 0,
-  and search for name, not inum
 is there a create/create race for same file name?
   resulting in two entries w/ same name in directory?
+why does shell often ignore first line of input?
 
 test: one process unlinks a file while another links to it
-test: simultaneous create of same file
 test: one process opens a file while another deletes it
-
-wdir should use writei, to avoid special-case block allocation
-  also readi
-  is dir locked? probably
+test: mkdir. deadlock d/.. vs ../d
+
+make proc[0] runnable
+cpu early tss and gdt
+how do we get cpu0 scheduler() to use mpstack, not proc[0].kstack?
+when iget() first sleeps, where does it longjmp to?
+maybe set up proc[0] to be runnable, with entry proc0main(), then
+  have main() call scheduler()?
+  perhaps so proc[0] uses right kstack?
+  and scheduler() uses mpstack?
+ltr sets the busy bit in the TSS, faults if already set
+  so gdt and TSS per cpu?
+  we don't want to be using some random process's gdt when it changes it.
+maybe get rid of per-proc gdt and ts
+  one per cpu
+  refresh it when needed
+  setupsegs(proc *)
diff --git a/fstests.c b/fstests.c
new file mode 100644
index 0000000000000000000000000000000000000000..d6f630e512a5745881563b063b12c5ce33b7f40f
--- /dev/null
+++ b/fstests.c
@@ -0,0 +1,380 @@
+#include "user.h"
+#include "fcntl.h"
+
+char buf[512];
+
+// two processes write to the same file descriptor
+// is the offset shared? does inode locking work?
+void
+sharedfd()
+{
+  int fd, pid, i, n, nc, np;
+  char buf[10];
+
+  unlink("sharedfd");
+  fd = open("sharedfd", O_CREATE|O_RDWR);
+  if(fd < 0){
+    printf(1, "fstests: cannot open sharedfd for writing");
+    return;
+  }
+  pid = fork();
+  memset(buf, pid==0?'c':'p', sizeof(buf));
+  for(i = 0; i < 100; i++){
+    if(write(fd, buf, sizeof(buf)) != sizeof(buf)){
+      printf(1, "fstests: write sharedfd failed\n");
+      break;
+    }
+  }
+  if(pid == 0)
+    exit();
+  else
+    wait();
+  close(fd);
+  fd = open("sharedfd", 0);
+  if(fd < 0){
+    printf(1, "fstests: cannot open sharedfd for reading\n");
+    return;
+  }
+  nc = np = 0;
+  while((n = read(fd, buf, sizeof(buf))) > 0){
+    for(i = 0; i < sizeof(buf); i++){
+      if(buf[i] == 'c')
+        nc++;
+      if(buf[i] == 'p')
+        np++;
+    }
+  }
+  close(fd);
+  unlink("sharedfd");
+  if(nc == 1000 && np == 1000)
+    printf(1, "sharedfd ok\n");
+  else
+    printf(1, "sharedfd oops %d %d\n", nc, np);
+}
+
+// two processes write two different files at the same
+// time, to test block allocation.
+void
+twofiles()
+{
+  int fd, pid, i, j, n, total;
+  char *fname;
+
+  unlink("f1");
+  unlink("f2");
+
+  pid = fork();
+  if(pid < 0){
+    puts("fork failed\n");
+    return;
+  }
+
+  fname = pid ? "f1" : "f2";
+  fd = open(fname, O_CREATE | O_RDWR);
+  if(fd < 0){
+    puts("create failed\n");
+    exit();
+  }
+
+  memset(buf, pid?'p':'c', 512);
+  for(i = 0; i < 12; i++){
+    if((n = write(fd, buf, 500)) != 500){
+      printf(1, "write failed %d\n", n);
+      exit();
+    }
+  }
+  close(fd);
+  if(pid)
+    wait();
+  else
+    exit();
+
+  for(i = 0; i < 2; i++){
+    fd = open(i?"f1":"f2", 0);
+    total = 0;
+    while((n = read(fd, buf, sizeof(buf))) > 0){
+      for(j = 0; j < n; j++){
+        if(buf[j] != (i?'p':'c')){
+          puts("wrong char\n");
+          exit();
+        }
+      }
+      total += n;
+    }
+    close(fd);
+    if(total != 12*500){
+      printf(1, "wrong length %d\n", total);
+      exit();
+    }
+  }
+
+  unlink("f1");
+  unlink("f2");
+
+  puts("twofiles ok\n");
+}
+
+// two processes create and delete files in same directory
+void
+createdelete()
+{
+  int pid, i, fd;
+  int n = 20;
+  char name[32];
+
+  pid = fork();
+  if(pid < 0){
+    puts("fork failed\n");
+    exit();
+  }
+
+  name[0] = pid ? 'p' : 'c';
+  name[2] = '\0';
+  for(i = 0; i < n; i++){
+    name[1] = '0' + i;
+    fd = open(name, O_CREATE | O_RDWR);
+    if(fd < 0){
+      puts("create failed\n");
+      exit();
+    }
+    close(fd);
+    if(i > 0 && (i % 2 ) == 0){
+      name[1] = '0' + (i / 2);
+      if(unlink(name) < 0){
+        puts("unlink failed\n");
+        exit();
+      }
+    }
+  }
+
+  if(pid)
+    wait();
+  else
+    exit();
+
+  for(i = 0; i < n; i++){
+    name[0] = 'p';
+    name[1] = '0' + i;
+    fd = open(name, 0);
+    if((i == 0 || i >= n/2) && fd < 0){
+      printf(1, "oops createdelete %s didn't exist\n", name);
+    } else if((i >= 1 && i < n/2) && fd >= 0){
+      printf(1, "oops createdelete %s did exist\n", name);
+    }
+    if(fd >= 0)
+      close(fd);
+
+    name[0] = 'c';
+    name[1] = '0' + i;
+    fd = open(name, 0);
+    if((i == 0 || i >= n/2) && fd < 0){
+      printf(1, "oops createdelete %s didn't exist\n", name);
+    } else if((i >= 1 && i < n/2) && fd >= 0){
+      printf(1, "oops createdelete %s did exist\n", name);
+    }
+    if(fd >= 0)
+      close(fd);
+  }
+
+  for(i = 0; i < n; i++){
+    name[0] = 'p';
+    name[1] = '0' + i;
+    unlink(name);
+    name[0] = 'c';
+    unlink(name);
+  }
+
+  printf(1, "createdelete ok\n");
+}
+
+// can I unlink a file and still read it?
+void
+unlinkread()
+{
+  int fd, fd1;
+  
+  fd = open("unlinkread", O_CREATE | O_RDWR);
+  if(fd < 0){
+    puts("create unlinkread failed\n");
+    exit();
+  }
+  write(fd, "hello", 5);
+  close(fd);
+
+  fd = open("unlinkread", O_RDWR);
+  if(fd < 0){
+    puts("open unlinkread failed\n");
+    exit();
+  }
+  if(unlink("unlinkread") != 0){
+    puts("unlink unlinkread failed\n");
+    exit();
+  }
+
+  fd1 = open("xxx", O_CREATE | O_RDWR);
+  write(fd1, "yyy", 3);
+  close(fd1);
+
+  if(read(fd, buf, sizeof(buf)) != 5){
+    puts("unlinkread read failed");
+    exit();
+  }
+  if(buf[0] != 'h'){
+    puts("unlinkread wrong data\n");
+    exit();
+  }
+  if(write(fd, buf, 10) != 10){
+    puts("unlinkread write failed\n");
+    exit();
+  }
+  close(fd);
+  unlink("xxx");
+  puts("unlinkread ok\n");
+}
+
+void
+linktest()
+{
+  int fd;
+
+  unlink("lf1");
+  unlink("lf2");
+
+  fd = open("lf1", O_CREATE|O_RDWR);
+  if(fd < 0){
+    puts("create lf1 failed\n");
+    exit();
+  }
+  if(write(fd, "hello", 5) != 5){
+    puts("write lf1 failed\n");
+    exit();
+  }
+  close(fd);
+
+  if(link("lf1", "lf2") < 0){
+    puts("link lf1 lf2 failed\n");
+    exit();
+  }
+  unlink("lf1");
+
+  if(open("lf1", 0) >= 0){
+    puts("unlinked lf1 but it is still there!\n");
+    exit();
+  }
+
+  fd = open("lf2", 0);
+  if(fd < 0){
+    puts("open lf2 failed\n");
+    exit();
+  }
+  if(read(fd, buf, sizeof(buf)) != 5){
+    puts("read lf2 failed\n");
+    exit();
+  }
+  close(fd);
+    
+  if(link("lf2", "lf2") >= 0){
+    puts("link lf2 lf2 succeeded! oops\n");
+    exit();
+  }
+
+  unlink("lf2");
+  if(link("lf2", "lf1") >= 0){
+    puts("link non-existant succeeded! oops\n");
+    exit();
+  }
+
+  if(link(".", "lf1") >= 0){
+    puts("link . lf1 succeeded! oops\n");
+    exit();
+  }
+
+  puts("linktest ok\n");
+}
+
+// test concurrent create of the same file
+void
+concreate()
+{
+  char file[3];
+  int i, pid, n, fd;
+  char fa[40];
+  struct {
+    unsigned short inum;
+    char name[14];
+  } de;
+
+  file[0] = 'C';
+  file[2] = '\0';
+  for(i = 0; i < 40; i++){
+    file[1] = '0' + i;
+    unlink(file);
+    pid = fork();
+    if(pid && (i % 3) == 1){
+      link("C0", file);
+    } else if(pid == 0 && (i % 5) == 1){
+      link("C0", file);
+    } else {
+      fd = open(file, O_CREATE | O_RDWR);
+      if(fd < 0){
+        puts("concreate create failed\n");
+        exit();
+      }
+      close(fd);
+    }
+    if(pid == 0)
+      exit();
+    else
+      wait();
+  }
+
+  memset(fa, 0, sizeof(fa));
+  fd = open(".", 0);
+  n = 0;
+  while(read(fd, &de, sizeof(de)) > 0){
+    if(de.inum == 0)
+      continue;
+    if(de.name[0] == 'C' && de.name[2] == '\0'){
+      i = de.name[1] - '0';
+      if(i < 0 || i >= sizeof(fa)){
+        printf(1, "concreate weird file %s\n", de.name);
+        exit();
+      }
+      if(fa[i]){
+        printf(1, "concreate duplicate file %s\n", de.name);
+        exit();
+      }
+      fa[i] = 1;
+      n++;
+    }
+  }
+  close(fd);
+
+  if(n != 40){
+    puts("concreate not enough files in directory listing\n");
+    exit();
+  }
+
+  for(i = 0; i < 40; i++){
+    file[1] = '0' + i;
+    unlink(file);
+  }
+
+  puts("concreate ok\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+  puts("fstests starting\n");
+
+  concreate();
+  linktest();
+  unlinkread();
+  createdelete();
+  twofiles();
+  sharedfd();
+
+  puts("fstests finished\n");
+  exit();
+}
diff --git a/main.c b/main.c
index d877208c092c76c9b66fca99d90c3d17423522a8..35ea6722bb40517824a1e510d7cd00bc473257f5 100644
--- a/main.c
+++ b/main.c
@@ -29,10 +29,14 @@ main0(void)
   // clear BSS
   memset(edata, 0, end - edata);
 
+  // switch to cpu0's cpu stack
+  asm volatile("movl %0, %%esp" : : "r" (cpus[0].mpstack + MPSTACK - 32));
+  asm volatile("movl %0, %%ebp" : : "r" (cpus[0].mpstack + MPSTACK));
+
   // Make sure interrupts stay disabled on all processors
   // until each signals it is ready, by pretending to hold
   // an extra lock.
-  // xxx maybe replace w/ acquire remembering if FL_IF
+  // xxx maybe replace w/ acquire remembering if FL_IF was already clear
   for(i=0; i<NCPU; i++){
     cpus[i].nlock++;
     cpus[i].guard1 = 0xdeadbeef;
@@ -55,16 +59,9 @@ main0(void)
   fd_init();
   iinit();
 
-  // create a fake process per CPU
-  // so each CPU always has a tss and a gdt
-  for(p = &proc[0]; p < &proc[NCPU]; p++){
-    p->state = IDLEPROC;
-    p->kstack = cpus[p-proc].mpstack;
-    p->pid = p - proc;
-  }
-
   // fix process 0 so that copyproc() will work
   p = &proc[0];
+  p->state = IDLEPROC;
   p->sz = 4 * PAGE;
   p->mem = kalloc(p->sz);
   memset(p->mem, 0, p->sz);
@@ -74,8 +71,9 @@ main0(void)
   p->tf->es = p->tf->ds = p->tf->ss = (SEG_UDATA << 3) | 3;
   p->tf->cs = (SEG_UCODE << 3) | 3;
   p->tf->eflags = FL_IF;
-  setupsegs(p);
-  // curproc[cpu()] = p;
+
+  // make sure there's a TSS
+  setupsegs(0);
 
   // initialize I/O devices, let them enable interrupts
   console_init();
@@ -117,7 +115,8 @@ mpmain(void)
   lapic_timerinit();
   lapic_enableintr();
 
-  setupsegs(&proc[cpu()]);
+  // make sure there's a TSS
+  setupsegs(0);
 
   cpuid(0, 0, 0, 0, 0);	// memory barrier
   cpus[cpu()].booted = 1;
diff --git a/proc.c b/proc.c
index 6880fd40ba843cb18896cfa7db45049e2ea63fa7..a7908e230593fc2f0f0d319e6716e15d69e22643 100644
--- a/proc.c
+++ b/proc.c
@@ -11,7 +11,7 @@ struct spinlock proc_table_lock;
 
 struct proc proc[NPROC];
 struct proc *curproc[NCPU];
-int next_pid = NCPU;
+int next_pid = 1;
 extern void forkret(void);
 extern void forkret1(struct trapframe*);
 
@@ -22,28 +22,39 @@ pinit(void)
 }
 
 /*
- * set up a process's task state and segment descriptors
- * correctly, given its current size and address in memory.
- * this should be called whenever the latter change.
- * doesn't change the cpu's current segmentation setup.
+ * set up CPU's segment descriptors and task state for a
+ * given process. If p==0, set up for "idle" state for
+ * when scheduler() isn't running any process.
  */
 void
 setupsegs(struct proc *p)
 {
-  memset(&p->ts, 0, sizeof(struct taskstate));
-  p->ts.ss0 = SEG_KDATA << 3;
-  p->ts.esp0 = (uint)(p->kstack + KSTACKSIZE);
+  struct cpu *c = &cpus[cpu()];
+
+  c->ts.ss0 = SEG_KDATA << 3;
+  if(p){
+    c->ts.esp0 = (uint)(p->kstack + KSTACKSIZE);
+  } else {
+    c->ts.esp0 = 0xffffffff;
+  }
 
   // XXX it may be wrong to modify the current segment table!
 
-  p->gdt[0] = SEG_NULL;
-  p->gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0x100000 + 64*1024, 0); // xxx
-  p->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0);
-  p->gdt[SEG_TSS] = SEG16(STS_T32A, (uint) &p->ts,
-                                sizeof(p->ts), 0);
-  p->gdt[SEG_TSS].s = 0;
-  p->gdt[SEG_UCODE] = SEG(STA_X|STA_R, (uint)p->mem, p->sz, 3);
-  p->gdt[SEG_UDATA] = SEG(STA_W, (uint)p->mem, p->sz, 3);
+  c->gdt[0] = SEG_NULL;
+  c->gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0x100000 + 64*1024, 0); // xxx
+  c->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0);
+  c->gdt[SEG_TSS] = SEG16(STS_T32A, (uint) &c->ts, sizeof(c->ts), 0);
+  c->gdt[SEG_TSS].s = 0;
+  if(p){
+    c->gdt[SEG_UCODE] = SEG(STA_X|STA_R, (uint)p->mem, p->sz, 3);
+    c->gdt[SEG_UDATA] = SEG(STA_W, (uint)p->mem, p->sz, 3);
+  } else {
+    c->gdt[SEG_UCODE] = SEG_NULL;
+    c->gdt[SEG_UDATA] = SEG_NULL;
+  }
+
+  lgdt(c->gdt, sizeof c->gdt);
+  ltr(SEG_TSS << 3);
 }
 
 // Look in the process table for an UNUSED proc.
@@ -101,9 +112,6 @@ copyproc(struct proc* p)
     np->state = UNUSED;
     return 0;
   }
-  
-  // Initialize segment table.
-  setupsegs(np);
 
   // Copy trapframe registers from parent.
   np->tf = (struct trapframe*)(np->kstack + KSTACKSIZE) - 1;
@@ -159,26 +167,11 @@ scheduler(void)
       if(p->state != RUNNABLE)
         continue;
       
-      // Run this process.
-      // XXX move this into swtch or trapret or something.
-      // It can run on the other stack.
-      // h/w sets busy bit in TSS descriptor sometimes, and faults
-      // if it's set in LTR. so clear tss descriptor busy bit.
-      p->gdt[SEG_TSS].type = STS_T32A;
-    
-      // XXX should probably have an lgdt() function in x86.h
-      // to confine all the inline assembly.
-      // XXX probably ought to lgdt on trap return too, in case
-      // a system call has moved a program or changed its size.
-      lgdt(p->gdt, sizeof p->gdt);
-    // asm volatile("lgdt %0" : : "g" (p->gdt_pd.lim));
-
-      ltr(SEG_TSS << 3);
-
       // Switch to chosen process.  It is the process's job 
       // to release proc_table_lock and then reacquire it
       // before jumping back to us.
-      if(0) cprintf("cpu%d: run %d\n", cpu(), p-proc);
+
+      setupsegs(p);
       curproc[cpu()] = p;
       p->state = RUNNING;
       if(setjmp(&cpus[cpu()].jmpbuf) == 0)
@@ -199,9 +192,7 @@ scheduler(void)
         panic("scheduler lock");
       }
 
-      setupsegs(&proc[cpu()]);
-
-      // XXX if not holding proc_table_lock panic.
+      setupsegs(0);
     }
 
     release(&proc_table_lock);
diff --git a/proc.h b/proc.h
index 88e630b262c299d94fbab415848a29b1c6c475b8..611f9b5f15a1ae8e98e374f2f671cbb724410813 100644
--- a/proc.h
+++ b/proc.h
@@ -48,8 +48,6 @@ struct proc{
   struct fd *fds[NOFILE];
   struct inode *cwd;
 
-  struct taskstate ts;  // only to give cpu address of kernel stack
-  struct segdesc gdt[NSEGS];
   uint esp; // kernel stack pointer
   uint ebp; // kernel frame pointer
 
@@ -67,6 +65,8 @@ extern struct proc *curproc[NCPU];  // can be NULL if no proc running.
 struct cpu {
   uchar apicid;       // Local APIC ID
   struct jmpbuf jmpbuf;
+  struct taskstate ts;  // only to give cpu address of kernel stack
+  struct segdesc gdt[NSEGS];
   int guard1;
   char mpstack[MPSTACK]; // per-cpu start-up stack
   int guard2;
diff --git a/trap.c b/trap.c
index 99aaa70ef7b28d960a38d32f7b95f138f3ea4e93..2bb3e9e26faf4909cde2a392fbcdac5021b06fe7 100644
--- a/trap.c
+++ b/trap.c
@@ -41,8 +41,8 @@ trap(struct trapframe *tf)
     panic("interrupt while holding a lock");
   }
 
-  if(cpu() == 1 && curproc[cpu()] == 0){
-    if(&tf < cpus[cpu()].mpstack || &tf > cpus[cpu()].mpstack + 512){
+  if(curproc[cpu()] == 0){
+    if(&tf < cpus[cpu()].mpstack || &tf > cpus[cpu()].mpstack + MPSTACK){
       cprintf("&tf %x mpstack %x\n", &tf, cpus[cpu()].mpstack);
       panic("trap cpu stack");
     }
@@ -125,7 +125,8 @@ trap(struct trapframe *tf)
     return;
   }
 
-  cprintf("trap %d\n", v);
+  cprintf("trap %d from cpu %d eip %x\n", v, cpu(), tf->eip);
+  panic("trap");
 
   return;
 }
diff --git a/usertests.c b/usertests.c
index c3e91133141d8d7be7c465cae53f201f658d3693..20155c2f2b43b86052582126cc960dc374261438 100644
--- a/usertests.c
+++ b/usertests.c
@@ -115,305 +115,11 @@ exitwait(void)
   puts("exitwait ok\n");
 }
 
-// two processes write to the same file descriptor
-// is the offset shared? does inode locking work?
-void
-sharedfd()
-{
-  int fd, pid, i, n, nc, np;
-  char buf[10];
-
-  unlink("sharedfd");
-  fd = open("sharedfd", O_CREATE|O_RDWR);
-  if(fd < 0){
-    printf(1, "usertests: cannot open sharedfd for writing");
-    return;
-  }
-  pid = fork();
-  memset(buf, pid==0?'c':'p', sizeof(buf));
-  for(i = 0; i < 100; i++){
-    if(write(fd, buf, sizeof(buf)) != sizeof(buf)){
-      printf(1, "usertests: write sharedfd failed\n");
-      break;
-    }
-  }
-  if(pid == 0)
-    exit();
-  else
-    wait();
-  close(fd);
-  fd = open("sharedfd", 0);
-  if(fd < 0){
-    printf(1, "usertests: cannot open sharedfd for reading\n");
-    return;
-  }
-  nc = np = 0;
-  while((n = read(fd, buf, sizeof(buf))) > 0){
-    for(i = 0; i < sizeof(buf); i++){
-      if(buf[i] == 'c')
-        nc++;
-      if(buf[i] == 'p')
-        np++;
-    }
-  }
-  close(fd);
-  unlink("sharedfd");
-  if(nc == 1000 && np == 1000)
-    printf(1, "sharedfd ok\n");
-  else
-    printf(1, "sharedfd oops %d %d\n", nc, np);
-}
-
-// two processes write two different files at the same
-// time, to test block allocation.
-void
-twofiles()
-{
-  int fd, pid, i, j, n, total;
-  char *fname;
-
-  unlink("f1");
-  unlink("f2");
-
-  pid = fork();
-  if(pid < 0){
-    puts("fork failed\n");
-    return;
-  }
-
-  fname = pid ? "f1" : "f2";
-  fd = open(fname, O_CREATE | O_RDWR);
-  if(fd < 0){
-    puts("create failed\n");
-    exit();
-  }
-
-  memset(buf, pid?'p':'c', 512);
-  for(i = 0; i < 12; i++){
-    if((n = write(fd, buf, 500)) != 500){
-      printf(1, "write failed %d\n", n);
-      exit();
-    }
-  }
-  close(fd);
-  if(pid)
-    wait();
-  else
-    exit();
-
-  for(i = 0; i < 2; i++){
-    fd = open(i?"f1":"f2", 0);
-    total = 0;
-    while((n = read(fd, buf, sizeof(buf))) > 0){
-      for(j = 0; j < n; j++){
-        if(buf[j] != (i?'p':'c')){
-          puts("wrong char\n");
-          exit();
-        }
-      }
-      total += n;
-    }
-    close(fd);
-    if(total != 12*500){
-      printf(1, "wrong length %d\n", total);
-      exit();
-    }
-  }
-
-  unlink("f1");
-  unlink("f2");
-
-  puts("twofiles ok\n");
-}
-
-// two processes create and delete files in same directory
-void
-createdelete()
-{
-  int pid, i, fd;
-  int n = 20;
-  char name[32];
-
-  pid = fork();
-  if(pid < 0){
-    puts("fork failed\n");
-    exit();
-  }
-
-  name[0] = pid ? 'p' : 'c';
-  name[2] = '\0';
-  for(i = 0; i < n; i++){
-    name[1] = '0' + i;
-    fd = open(name, O_CREATE | O_RDWR);
-    if(fd < 0){
-      puts("create failed\n");
-      exit();
-    }
-    close(fd);
-    if(i > 0 && (i % 2 ) == 0){
-      name[1] = '0' + (i / 2);
-      if(unlink(name) < 0){
-        puts("unlink failed\n");
-        exit();
-      }
-    }
-  }
-
-  if(pid)
-    wait();
-  else
-    exit();
-
-  for(i = 0; i < n; i++){
-    name[0] = 'p';
-    name[1] = '0' + i;
-    fd = open(name, 0);
-    if((i == 0 || i >= n/2) && fd < 0){
-      printf(1, "oops createdelete %s didn't exist\n", name);
-    } else if((i >= 1 && i < n/2) && fd >= 0){
-      printf(1, "oops createdelete %s did exist\n", name);
-    }
-    if(fd >= 0)
-      close(fd);
-
-    name[0] = 'c';
-    name[1] = '0' + i;
-    fd = open(name, 0);
-    if((i == 0 || i >= n/2) && fd < 0){
-      printf(1, "oops createdelete %s didn't exist\n", name);
-    } else if((i >= 1 && i < n/2) && fd >= 0){
-      printf(1, "oops createdelete %s did exist\n", name);
-    }
-    if(fd >= 0)
-      close(fd);
-  }
-
-  for(i = 0; i < n; i++){
-    name[0] = 'p';
-    name[1] = '0' + i;
-    unlink(name);
-    name[0] = 'c';
-    unlink(name);
-  }
-
-  printf(1, "createdelete ok\n");
-}
-
-// can I unlink a file and still read it?
-void
-unlinkread()
-{
-  int fd, fd1;
-  
-  fd = open("unlinkread", O_CREATE | O_RDWR);
-  if(fd < 0){
-    puts("create unlinkread failed\n");
-    exit();
-  }
-  write(fd, "hello", 5);
-  close(fd);
-
-  fd = open("unlinkread", O_RDWR);
-  if(fd < 0){
-    puts("open unlinkread failed\n");
-    exit();
-  }
-  if(unlink("unlinkread") != 0){
-    puts("unlink unlinkread failed\n");
-    exit();
-  }
-
-  fd1 = open("xxx", O_CREATE | O_RDWR);
-  write(fd1, "yyy", 3);
-  close(fd1);
-
-  if(read(fd, buf, sizeof(buf)) != 5){
-    puts("unlinkread read failed");
-    exit();
-  }
-  if(buf[0] != 'h'){
-    puts("unlinkread wrong data\n");
-    exit();
-  }
-  if(write(fd, buf, 10) != 10){
-    puts("unlinkread write failed\n");
-    exit();
-  }
-  close(fd);
-  unlink("xxx");
-  puts("unlinkread ok\n");
-}
-
-void
-linktest()
-{
-  int fd;
-
-  unlink("lf1");
-  unlink("lf2");
-
-  fd = open("lf1", O_CREATE|O_RDWR);
-  if(fd < 0){
-    puts("create lf1 failed\n");
-    exit();
-  }
-  if(write(fd, "hello", 5) != 5){
-    puts("write lf1 failed\n");
-    exit();
-  }
-  close(fd);
-
-  if(link("lf1", "lf2") < 0){
-    puts("link lf1 lf2 failed\n");
-    exit();
-  }
-  unlink("lf1");
-
-  if(open("lf1", 0) >= 0){
-    puts("unlinked lf1 but it is still there!\n");
-    exit();
-  }
-
-  fd = open("lf2", 0);
-  if(fd < 0){
-    puts("open lf2 failed\n");
-    exit();
-  }
-  if(read(fd, buf, sizeof(buf)) != 5){
-    puts("read lf2 failed\n");
-    exit();
-  }
-  close(fd);
-    
-  if(link("lf2", "lf2") >= 0){
-    puts("link lf2 lf2 succeeded! oops\n");
-    exit();
-  }
-
-  unlink("lf2");
-  if(link("lf2", "lf1") >= 0){
-    puts("link non-existant succeeded! oops\n");
-    exit();
-  }
-
-  if(link(".", "lf1") >= 0){
-    puts("link . lf1 succeeded! oops\n");
-    exit();
-  }
-
-  puts("linktest ok\n");
-}
-
 int
 main(int argc, char *argv[])
 {
   puts("usertests starting\n");
 
-  linktest();
-  unlinkread();
-  createdelete();
-  twofiles();
-  sharedfd();
   pipe1();
   preempt();
   exitwait();