Blame | Last modification | View Log | RSS feed
/*
* This implementation of a memory allocation utilizes a segregated fits
* appraoch. Multiple buckets are used for each size range and a doubly
* linked list used to hold the list of free blocks for each bucket.
* Free blocks for each bucket is choosen based on first fit, with the
* search going into the next sized bucket if no fit is found in the
* current bucket. If no free blocks are found that holds the requested
* size, more memory is requested by incrementing the brk pointer using
* mem_sbrk. The requested memory is in a multiple of the page size for
* higher allocating efficiency. Freeing a previously allocated block
* coalesces the block with the previous and next blocks if they are
* empty. The last 4 bytes in each block is used as a boundary tag that
* holds the size of the block it is in. This allows the code to
* traverse backwards in the address space and check if the previous
* block is empty or allocated.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stddef.h>
#include <limits.h>
#include "mm.h"
#include "memlib.h"
#include "list.h"
#include "config.h" /* defines ALIGNMENT */
#define FREE_SPACE_SPLIT_OVERHEAD 512
/*********************************************************
* NOTE TO STUDENTS: Before you do anything else, please
* provide your team information in the following struct.
********************************************************/
team_t team = {
/* Team name */
"Team K",
/* First member's full name */
"Kevin Lee",
/* First member's SLO (@cs.vt.edu) email address */
"klee482@vt.edu",
/* Second member's full name (leave blank if none) */
"",
/* Second member's SLO (@cs.vt.edu) email address (leave blank if none) */
""
};
/*
* If size is a multiple of ALIGNMENT, return size.
* Else, return next larger multiple of ALIGNMENT:
* (size/ALIGNMENT + 1) * ALIGNMENT
* Does so without requiring integer division, assuming
* ALIGNMENT is a power of 2.
*/
static size_t roundup(size_t size)
{
return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1);
}
/*
* This C struct captures an allocated header.
*
* By casting a memory location to a pointer to a allocated_block_header,
* we are able to treat a part of memory as if a header had been allocated
* in it.
*
* Note: you should never define instances of 'struct allocated_block_header' -
* all accesses will be through pointers.
*/
struct allocated_block_header {
size_t size; // Size of the block, includes header and padding
/*
* Zero length arrays do not add size to the structure, they simply
* provide a syntactic form to refer to a char array following the
* structure.
* See http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
*
* The 'aligned' attribute forces 'payload' to be aligned at a
* multiple of alignment, counted from the beginning of the struct
* See http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
*/
char payload[0] __attribute__((aligned(ALIGNMENT)));
};
/*
* This C struct captures a free header.
*
* By casting a memory location to a pointer to a free_block_header,
* we are able to treat a part of memory as if a header had been allocated
* in it.
*/
struct free_block_header {
size_t size; // Size of the block, includes header and padding
struct list_elem elem; // Allows the struct to be used in a doublely linked list
};
/*
* This C struct captures a bucket of free blocks
*
* This struct is an element of a list that holds list of free blocks as
* well as relevant information for the bucket (size of free blocks).
*/
struct free_list_index {
struct list free_block_list; // List of free blocks
int size_min; // Minimum size of each block
int size_max; // Maximum size of each block
struct list_elem elem; // Allows the struct to be used in a doublely linked list
};
/*
* This C struct captures a boundary tag
*
* By casting a memory location to a pointer to a boundary tag,
* we can read the size of the block from the front or the back.
*/
struct boundary_block {
size_t size;
};
// Global list holding the main list of buckets and each bucket's list
struct list index_of_free_lists;
struct free_list_index bucket_1 = {.size_min = 1, .size_max = 2};
struct free_list_index bucket_2 = {.size_min = 3, .size_max = 8};
struct free_list_index bucket_3 = {.size_min = 9, .size_max = 32};
struct free_list_index bucket_4 = {.size_min = 33, .size_max = 128};
struct free_list_index bucket_5 = {.size_min = 129, .size_max = 512};
struct free_list_index bucket_6 = {.size_min = 513, .size_max = 1024};
struct free_list_index bucket_7 = {.size_min = 1025, .size_max = 2048};
struct free_list_index bucket_8 = {.size_min = 2049, .size_max = 4096};
struct free_list_index bucket_9 = {.size_min = 4097, .size_max = 8192};
struct free_list_index bucket_10 = {.size_min = 8193, .size_max = 16384};
struct free_list_index bucket_11 = {.size_min = 16385, .size_max = 32768};
struct free_list_index bucket_12 = {.size_min = 32769, .size_max = 65536};
struct free_list_index bucket_13 = {.size_min = 65537, .size_max = INT_MAX};
/*
* alloc_block_set_boundary_tags - Sets the boundary tags for allocated blocks
*/
void alloc_block_set_boundary_tags(void *alloc_block) {
struct allocated_block_header *block = alloc_block;
int size = block->size;
block->size |= 1;
struct boundary_block *f = alloc_block + size - sizeof(size_t);
f->size = size |= 1;
}
/*
* free_block_set_boundary_tags - Sets the boundary tags for free blocks
*/
void free_block_set_boundary_tags(void *free_block) {
struct free_block_header *block = free_block;
struct boundary_block *f = free_block + block->size - sizeof(size_t);
f->size = block->size;
}
/*
* free_block_insert - Takes a free block and pushes it to the front of the
* correct list that it fits in. Also coalesces the block with the blocks
* before and after it in the address space.
*/
void free_block_insert(void *free_block) {
struct free_block_header *block = free_block;
// Check if the block can be combined with any free blocks before or after
bool prev_block_empty = false;
bool next_block_empty = false;
struct boundary_block *prev = free_block - sizeof(size_t);
struct boundary_block *next = free_block + block->size;
if (free_block != mem_heap_lo()) {
if ((prev->size & 1) == 0) {
prev_block_empty = true;
}
}
if (free_block + block->size != mem_heap_hi() + 1) {
if ((next->size & 1) == 0) {
next_block_empty = true;
}
}
// Combine blocks accordingly
if (prev_block_empty && next_block_empty) {
// Both previous and next blocks are empty
struct free_block_header *prev_block = free_block - prev->size;
struct free_block_header *next_block = free_block + block->size;
list_remove(&prev_block->elem);
list_remove(&next_block->elem);
prev_block->size += block->size;
prev_block->size += next_block->size;
block = prev_block;
} else if (prev_block_empty && !next_block_empty) {
// Only the previous block is empty
struct free_block_header *prev_block = free_block - prev->size;
list_remove(&prev_block->elem);
prev_block->size += block->size;
block = prev_block;
} else if (!prev_block_empty && next_block_empty) {
// Only the next block is empty
struct free_block_header *next_block = free_block + block->size;
list_remove(&next_block->elem);
block->size += next_block->size;
}
free_block_set_boundary_tags(block);
// Insert the free block into the bucket that it belongs to
struct list_elem *index = list_begin(&index_of_free_lists);
while (index != list_end(&index_of_free_lists)) {
struct free_list_index *index_entry = list_entry(index, struct free_list_index, elem);
if (block->size >= index_entry->size_min && block->size <= index_entry->size_max) {
list_push_front(&index_entry->free_block_list, &block->elem);
return;
}
index = list_next(index);
}
}
/*
* free_block_alloc - Uses the free space specified in free_block_header to
* hold a new allocated block of size alloc_size.
*/
void *free_block_alloc(void *free_block, size_t alloc_size, bool block_in_list) {
struct free_block_header *block = free_block;
if (block_in_list)
list_remove(&block->elem);
// Before we split up the free block, we need to check if there is enough
// space for holding another free block header.
if (block->size - alloc_size > sizeof(struct free_block_header)+FREE_SPACE_SPLIT_OVERHEAD) {
// There is enough space for another free block + overhead
int combined_size = block->size;
block = free_block + alloc_size;
block->size = combined_size - alloc_size;
struct allocated_block_header *ret = free_block;
ret->size = alloc_size;
alloc_block_set_boundary_tags(ret);
free_block_insert(block);
return ret;
} else {
// If there is not enough space for a free block header, simply
// convert to an allocated block header and return
struct allocated_block_header *ret = free_block;
alloc_block_set_boundary_tags(ret);
return ret;
}
}
/*
* free_block_search - Given a list of free_block_headers, finds
* and returns the first block that can hold the requested size.
* If no blocks were found, returns NULL.
*/
void *free_block_search(void *free_block_list, int requested_size) {
struct list *block_list = free_block_list;
struct list_elem *e = list_begin(block_list);
while (e != list_end(block_list)) {
struct free_block_header *entry= list_entry(e, struct free_block_header, elem);
if (entry->size >= requested_size) {
return entry;
}
e = list_next(e);
}
return NULL;
}
/*
* mm_check - Checks the heap for consistency. Returns a non-zero value
* only if the heap is consistent.
*/
int mm_check(void)
{
// Prints out the free blocks in each bucket when called
printf("Heap allocated from %d to %d of size %d\n", (int)mem_heap_lo(), (int)mem_heap_hi(), mem_heapsize());
printf("----------------------------------------\n");
struct list_elem *index = list_begin(&index_of_free_lists);
while (index != list_end(&index_of_free_lists)) {
struct free_list_index *index_entry = list_entry(index, struct free_list_index, elem);
printf("Bucket of size %d - %d:\n", index_entry->size_min, index_entry->size_max);
struct list_elem *entry = list_begin(&index_entry->free_block_list);
while(entry != list_end(&index_entry->free_block_list)) {
struct free_block_header *block = list_entry(entry, struct free_block_header, elem);
printf("\tFree Block at %d of size %d\n", (int)block, block->size);
entry = list_next(entry);
}
index = list_next(index);
}
printf("----------------------------------------\n");
return 1;
}
/*
* mm_init - Initialize the malloc package.
*/
int mm_init(void)
{
/* Sanity checks. */
assert((ALIGNMENT & (ALIGNMENT - 1)) == 0); // power of 2
assert(sizeof(struct allocated_block_header) == ALIGNMENT);
assert(offsetof(struct allocated_block_header, size) == 0);
assert(offsetof(struct allocated_block_header, payload) % ALIGNMENT == 0);
// Initialize the overall list of free lists
list_init(&index_of_free_lists);
// Initialize the list for each bucket size
list_init(&bucket_1.free_block_list);
list_init(&bucket_2.free_block_list);
list_init(&bucket_3.free_block_list);
list_init(&bucket_4.free_block_list);
list_init(&bucket_5.free_block_list);
list_init(&bucket_6.free_block_list);
list_init(&bucket_7.free_block_list);
list_init(&bucket_8.free_block_list);
list_init(&bucket_9.free_block_list);
list_init(&bucket_10.free_block_list);
list_init(&bucket_11.free_block_list);
list_init(&bucket_12.free_block_list);
list_init(&bucket_13.free_block_list);
// Insert the list into the main list
list_push_back(&index_of_free_lists, &bucket_1.elem);
list_push_back(&index_of_free_lists, &bucket_2.elem);
list_push_back(&index_of_free_lists, &bucket_3.elem);
list_push_back(&index_of_free_lists, &bucket_4.elem);
list_push_back(&index_of_free_lists, &bucket_5.elem);
list_push_back(&index_of_free_lists, &bucket_6.elem);
list_push_back(&index_of_free_lists, &bucket_7.elem);
list_push_back(&index_of_free_lists, &bucket_8.elem);
list_push_back(&index_of_free_lists, &bucket_9.elem);
list_push_back(&index_of_free_lists, &bucket_10.elem);
list_push_back(&index_of_free_lists, &bucket_11.elem);
list_push_back(&index_of_free_lists, &bucket_12.elem);
list_push_back(&index_of_free_lists, &bucket_13.elem);
return 0;
}
/*
* mm_malloc - Allocate a block by incrementing the brk pointer.
* Always allocate a block whose size is a multiple of the alignment.
*/
void *mm_malloc(size_t size)
{
int newsize = roundup(size + sizeof(struct free_block_header));
int searchsize = newsize;
// Look for free block list that contains the right sized blocks
struct list_elem *index = list_begin(&index_of_free_lists);
while (index != list_end(&index_of_free_lists)) {
struct free_list_index *index_entry = list_entry(index, struct free_list_index, elem);
if (searchsize >= index_entry->size_min && searchsize <= index_entry->size_max) {
// Try to get a free block that is large enough to hold the requested size
struct free_block_header *free_block = free_block_search(&index_entry->free_block_list, newsize);
if (free_block == NULL) {
// List is empty, look at the next block size
searchsize = index_entry->size_max + 1;
} else {
// Empty block was found, use the block to allocate space
struct allocated_block_header *alloc_block = free_block_alloc(free_block, newsize, true);
return alloc_block->payload;
}
}
index = list_next(index);
}
// If no blocks were found, expand the heap and allocate the block from there
// Calculage how many pages are needed to fit the block
int alloc_size = (newsize / mem_pagesize() + 1) * mem_pagesize();
// Get a number of pages from the heap and allocate from there
struct free_block_header *free_block = mem_sbrk(alloc_size);
if (free_block == NULL)
return NULL;
free_block->size = alloc_size;
struct allocated_block_header *alloc_block = free_block_alloc(free_block, newsize, false);
return alloc_block->payload;
}
/*
* mm_free - Frees the specified block, inserting it into the list of free blocks
*/
void mm_free(void *ptr)
{
// Get a pointer to the original block
struct free_block_header *new_block = ptr - offsetof(struct allocated_block_header, payload);
// Mark the block as free and insert it into the list of free blocks
new_block->size ^= 1;
free_block_insert(new_block);
}
/*
* mm_realloc - Resizes the specified block, leaving it if the size is smaller and allocating
* and copying the data to a new block if the size is larger. Extra space is left for
* future resizing.
*/
void *mm_realloc(void *oldptr, size_t size)
{
struct allocated_block_header *old_block = oldptr - offsetof(struct allocated_block_header, payload);
// old_block->size ^= 1;
// If the block is already large enough to hold the new size, simply return
if (size <= old_block->size - sizeof(struct free_block_header) - sizeof(size_t))
return old_block->payload;
// // Otherwise we need to allocate a new block of the request size
void *new_block = mm_malloc(size + 6 * mem_pagesize());
if (new_block == NULL)
return NULL;
// Copy the old data to the new block
size_t copySize = old_block->size;
if (size < copySize)
copySize = size;
memcpy(new_block, oldptr, copySize);
// Free the old block and return the new one
mm_free(oldptr);
return new_block;
}