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