Make tests public. Rewrite most tests. Add tests.
[pintos-anon] / src / threads / malloc.c
index 441ae59a0bfdaa2216c264bfaed51eeefb2d569c..2bb5571e230d3ab483f793729a6c484960b0dc53 100644 (file)
@@ -1,6 +1,7 @@
 #include "threads/malloc.h"
 #include <debug.h>
 #include <list.h>
+#include <round.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
    blocks, we remove all of the arena's blocks from the free list
    and give the arena back to the page allocator.
 
-   Major limitation: the largest block that can be allocated is
-   PGSIZE / 2, or 2 kB.  Use palloc_get() to allocate pages (4 kB
-   blocks).  You're on your own if you need more memory than
-   that. */
+   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
@@ -41,21 +43,25 @@ struct desc
     struct lock lock;           /* Lock. */
   };
 
+/* Magic number for detecting arena corruption. */
+#define ARENA_MAGIC 0x9a548eed
+
 /* Arena. */
 struct arena 
   {
-    struct desc *desc;          /* Owning descriptor. */
-    size_t free_cnt;            /* Number of free blocks. */
+    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 
   {
-    list_elem free_elem;        /* Free list element. */
+    struct list_elem free_elem; /* Free list element. */
   };
 
 /* Our set of descriptors. */
-static struct desc descs[16];   /* Descriptors. */
+static struct desc descs[10];   /* Descriptors. */
 static size_t desc_cnt;         /* Number of descriptors. */
 
 static struct arena *block_to_arena (struct block *);
@@ -67,14 +73,14 @@ malloc_init (void)
 {
   size_t block_size;
 
-  for (block_size = 16; block_size < PGSIZE; block_size *= 2)
+  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, "malloc");
+      lock_init (&d->lock);
     }
 }
 
@@ -94,12 +100,23 @@ malloc (size_t size)
   /* Find the smallest descriptor that satisfies a SIZE-byte
      request. */
   for (d = descs; d < descs + desc_cnt; d++)
-    if (size < d->block_size)
+    if (d->block_size >= size)
       break;
   if (d == descs + desc_cnt) 
     {
-      printf ("malloc: %zu byte allocation too big\n", size);
-      return NULL; 
+      /* 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);
@@ -110,7 +127,7 @@ malloc (size_t size)
       size_t i;
 
       /* Allocate a page. */
-      a = palloc_get (0);
+      a = palloc_get_page (0);
       if (a == NULL) 
         {
           lock_release (&d->lock);
@@ -118,11 +135,12 @@ malloc (size_t size)
         }
 
       /* 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++) 
         {
-          b = arena_to_block (a, i);
+          struct block *b = arena_to_block (a, i);
           list_push_back (&d->free_list, &b->free_elem);
         }
     }
@@ -143,7 +161,7 @@ calloc (size_t a, size_t b)
   void *p;
   size_t size;
 
-  /* Calculate block size. */
+  /* Calculate block size and make sure it fits in size_t. */
   size = a * b;
   if (size < a || size < b)
     return NULL;
@@ -156,51 +174,119 @@ calloc (size_t a, size_t b)
   return p;
 }
 
-/* Frees block P, which must have been previously allocated with
-   malloc() or calloc(). */
-void
-free (void *p) 
+/* Returns the number of bytes allocated for BLOCK. */
+static size_t
+block_size (void *block) 
 {
-  struct block *b = p;
+  struct block *b = block;
   struct arena *a = block_to_arena (b);
   struct desc *d = a->desc;
 
-  if (p == NULL)
-    return;
-
-  lock_acquire (&d->lock);
+  return d != NULL ? d->block_size : PGSIZE * a->free_cnt - pg_ofs (block);
+}
 
-  /* Add block to free list. */
-  list_push_front (&d->free_list, &b->free_elem);
+/* 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;
+    }
+}
 
-  /* If the arena is now entirely unused, free it. */
-  if (++a->free_cnt >= d->blocks_per_arena) 
+/* Frees block P, which must have been previously allocated with
+   malloc() or calloc(). */
+void
+free (void *p) 
+{
+  if (p != NULL)
     {
-      size_t i;
+      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);
+            }
 
-      ASSERT (a->free_cnt == d->blocks_per_arena);
-      for (i = 0; i < d->blocks_per_arena; i++) 
+          lock_release (&d->lock);
+        }
+      else
         {
-          struct block *b = arena_to_block (a, i);
-          list_remove (&b->free_elem);
+          /* It's a big block.  Free its pages. */
+          palloc_free_multiple (a, a->free_cnt);
+          return;
         }
-      palloc_free (a);
     }
-
-  lock_release (&d->lock);
 }
 \f
 /* Returns the arena that block B is inside. */
 static struct arena *
 block_to_arena (struct block *b)
 {
-  return pg_round_down (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