From 83d2db91f75460e1275d67847adec0fca5a9800b Mon Sep 17 00:00:00 2001
From: Robert Morris <rtm@nephron.lcs.mit.edu>
Date: Tue, 10 Aug 2010 17:08:41 -0400
Subject: [PATCH] allow sbrk(-x) to de-allocate user memory

---
 defs.h      |  1 +
 proc.c      |  9 +++++++--
 usertests.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 vm.c        | 26 +++++++++++++++++++++++-
 4 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/defs.h b/defs.h
index b691099..a051522 100644
--- a/defs.h
+++ b/defs.h
@@ -159,6 +159,7 @@ void            vminit(void);
 pde_t*          setupkvm(void);
 char*           uva2ka(pde_t*, char*);
 int             allocuvm(pde_t*, char*, uint);
+int             deallocuvm(pde_t *pgdir, char *addr, uint sz);
 void            freevm(pde_t*);
 void            inituvm(pde_t*, char*, char*, uint);
 int             loaduvm(pde_t*, char*, struct inode *ip, uint, uint);
diff --git a/proc.c b/proc.c
index f799a4d..e69bacf 100644
--- a/proc.c
+++ b/proc.c
@@ -142,8 +142,13 @@ userinit(void)
 int
 growproc(int n)
 {
-  if (!allocuvm(proc->pgdir, (char *)proc->sz, n))
-    return -1;
+  if(n > 0){
+    if (!allocuvm(proc->pgdir, (char *)proc->sz, n))
+      return -1;
+  } else if(n < 0){
+    if (!deallocuvm(proc->pgdir, (char *)(proc->sz + n), 0 - n))
+      return -1;
+  }
   proc->sz += n;
   switchuvm(proc);
   return 0;
diff --git a/usertests.c b/usertests.c
index 247cc95..9ad6448 100644
--- a/usertests.c
+++ b/usertests.c
@@ -1232,7 +1232,11 @@ forktest(void)
 void
 sbrktest(void)
 {
+  int pid;
+
   printf(stdout, "sbrk test\n");
+
+  // can one sbrk() less than a page?
   char *a = sbrk(0);
   int i;
   for(i = 0; i < 5000; i++){
@@ -1244,7 +1248,7 @@ sbrktest(void)
     *b = 1;
     a = b + 1;
   }
-  int pid = fork();
+  pid = fork();
   if(pid < 0){
     printf(stdout, "sbrk test fork failed\n");
     exit();
@@ -1258,6 +1262,57 @@ sbrktest(void)
   if(pid == 0)
     exit();
   wait();
+
+  // can one allocate the full 640K?
+  a = sbrk(0);
+  uint amt = (640 * 1024) - (uint) a;
+  char *p = sbrk(amt);
+  if(p != a){
+    printf(stdout, "sbrk test failed 640K test, p %x a %x\n", p, a);
+    exit();
+  }
+  char *lastaddr = (char *)(640 * 1024 - 1);
+  *lastaddr = 99;
+
+  // is one forbidden from allocating more than 640K?
+  c = sbrk(4096);
+  if(c != (char *) 0xffffffff){
+    printf(stdout, "sbrk allocated more than 640K, c %x\n", c);
+    exit();
+  }
+
+  // can one de-allocate?
+  a = sbrk(0);
+  c = sbrk(-4096);
+  if(c == (char *) 0xffffffff){
+    printf(stdout, "sbrk could not deallocate\n");
+    exit();
+  }
+  c = sbrk(0);
+  if(c != a - 4096){
+    printf(stdout, "sbrk deallocation produced wrong address, a %x c %x\n", a, c);
+    exit();
+  }
+
+  // can one re-allocate that page?
+  a = sbrk(0);
+  c = sbrk(4096);
+  if(c != a || sbrk(0) != a + 4096){
+    printf(stdout, "sbrk re-allocation failed, a %x c %x\n", a, c);
+    exit();
+  }
+  if(*lastaddr == 99){
+    // should be zero
+    printf(stdout, "sbrk de-allocation didn't really deallocate\n");
+    exit();
+  }
+
+  c = sbrk(4096);
+  if(c != (char *) 0xffffffff){
+    printf(stdout, "sbrk was able to re-allocate beyond 640K, c %x\n", c);
+    exit();
+  }
+
   printf(stdout, "sbrk test OK\n");
 }
 
diff --git a/vm.c b/vm.c
index 6914dd3..8755b82 100644
--- a/vm.c
+++ b/vm.c
@@ -198,7 +198,7 @@ uva2ka(pde_t *pgdir, char *uva)
 int
 allocuvm(pde_t *pgdir, char *addr, uint sz)
 {
-  if (addr + sz >= (char*)USERTOP)
+  if (addr + sz > (char*)USERTOP)
     return 0;
   char *first = PGROUNDDOWN(addr);
   char *last = PGROUNDDOWN(addr + sz - 1);
@@ -218,6 +218,30 @@ allocuvm(pde_t *pgdir, char *addr, uint sz)
   return 1;
 }
 
+// deallocate some of the user pages, in response to sbrk()
+// with a negative argument. if addr is not page-aligned,
+// then only deallocates starting at the next page boundary.
+int
+deallocuvm(pde_t *pgdir, char *addr, uint sz)
+{
+  if (addr + sz > (char*)USERTOP)
+    return 0;
+  char *first = (char*) PGROUNDUP((uint)addr);
+  char *last = PGROUNDDOWN(addr + sz - 1);
+  char *a;
+  for(a = first; a <= last; a += PGSIZE){
+    pte_t *pte = walkpgdir(pgdir, a, 0);
+    if(pte && (*pte & PTE_P) != 0){
+      uint pa = PTE_ADDR(*pte);
+      if(pa == 0)
+        panic("deallocuvm");
+      kfree((void *) pa, PGSIZE);
+      *pte = 0;
+    }
+  }
+  return 1;
+}
+
 // free a page table and all the physical memory pages
 // in the user part.
 void