The memory model using an implicit free list consists of
- prolog block
- regular blocks (some free, some allocated)
- epilog block
Prolog block |
|
Epilog block |
| hd |
ft |
hd |
free |
ft |
hd |
alloc |
ft |
hd |
alloc |
ft |
hd |
free |
ft |
hd |
ft |
All blocks have a 4 byte header and a 4 byte footer
The header and footer have the same value.
The value is in two parts:
The block size is in the upper 29 bits of the header/footer
and the 3 lower bits of the size are implied and are 0. This
means the block size is a multiple of 8.
The payload is the portion of the block between the
header and footer.
The payload must begin at an 8 byte address boundary; that
is, the payload address must be a multiple of 8.
The prolog block has a header and a footer and an empty payload and so the size of the
prolog block is 8.
The prolog block is marked as allocated.
So in binary, the header is
0000 0000 0000 0000 0000 0000 0000 1001
As an integer, this looks like 9, but the lower order 3 bits
are not part of the size. The size has 3 implied (not stored) low
order bits equal to 0:
0000 0000 0000 0000 0000 0000 0000 1000
which is the value 8
It is convenient to write the header value as 8/1:
size 8 and allocated bit = 1.
The epilog block also only has a header and footer with empty
payload.
The header value for the eplilog block is
0/1
size 0 and allocated bit = 1.
The actual size of the epilog is really 8, but setting the
size value in the header to 0 makes it unique among all blocks
and so it can be used to detect the end of the heap area when
searching the blocks.
The functions in the memlib.c file for managing the heap:
- void mem_init(int size)
- void * mem_sbrk(int incr)
- void * mem_heap_lo()
- void * mem_heap_hi()
- size_t mem_heapsize()
You should not change anything in these functions.
The mm.c file is the only file you will change.
You make work in teams of 1 or at most 2 people.
This file has a C struct in which you should insert your name
(or names if two people are working together).
You can omit the email addresses:
team_t team = {
/* Team name */
"asmith+asaini",
/* First member's full name */
"Afred Smith",
/* First member's email address */
"",
/* Second member's full name (leave blank if none) */
"Atul Saini",
/* Second member's email address (leave blank if none) */
""
};
- void * mm_malloc(size_t sz)
- void mm_free(void *ptr)
- mm_realloc(void * ptr, size_t sz)
but mm_init is called first and then these call other
functions. You may also need to add some
functions and some macros!
- int mm_init(void)
- void *find_fit(size_t asize)
- void place(void *bp, size_t asize)
- void *extend_heap(size_t words)
- void *coalesce(void *bp)
- void mm_checkheap(int verbose)
A very simple initial version of mm_malloc allocates 8 extra bytes
for the header and footer in addition to the payload:
/*
* 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 = ALIGN(size + 8);
void *p = mem_sbrk(newsize);
if ((int)p == -1)
return NULL;
else {
*(size_t *)p = (newsize | 1);
*(size_t *)(p + newsize - 4) = (newsize | 1);
return (void *)(p + 4);
}
}
A correspondingly simple version of mm_free is:
/*
* mm_free - Freeing a block does nothing.
*/
void mm_free(void *ptr)
{
}
The text has a much better version of mm_malloc (page
750).
This file is also available in the file mm.c provided in
the tar file for lab2.
The initial versions of mm_malloc, mm_free, and mm_realloc are
provided in the file mm.c. Here is that version of mm_malloc:
1 void *mm_malloc(size_t size)
2 {
3 size_t asize; /* adjusted block size */
4 size_t extendsize; /* amount to extend heap if no fit */
5 char *bp;
6
7 /* Ignore spurious requests */
8 if (size <= 0)
9 return NULL;
10
11 /* Adjust block size to include overhead and alignment reqs. */
12 if (size <= DSIZE)
13 asize = DSIZE + OVERHEAD;
14 else
15 asize = DSIZE * ((size + (OVERHEAD) + (DSIZE-1)) / DSIZE);
16
17 /* Search the free list for a fit */
18 if ((bp = find_fit(asize)) != NULL) {
19 place(bp, asize);
20 return bp;
21 }
22
23 /* No fit found. Get more memory and place the block */
24 extendsize = MAX(asize,CHUNKSIZE);
25 if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
26 return NULL;
27 place(bp, asize);
28 return bp;
29 }
The file mm.c with the "better" version of mm_malloc also
contans some auxilliary functions you can use including:
- extend_heap (p 748)
- coalesce (p 749)
static void *extend_heap(size_t words)
{
char *bp;
size_t size;
/* Allocate an even number of words to maintain alignment */
size = (words % 2) ? (words+1) * WSIZE : words * WSIZE;
if ((int)(bp = mem_sbrk(size)) < 0)
return NULL;
/* Initialize free block header/footer and the epilogue header */
PUT(HDRP(bp), PACK(size, 0)); /* free block header */
PUT(FTRP(bp), PACK(size, 0)); /* free block footer */
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); /* new epilogue header */
/* Coalesce if the previous block was free */
return coalesce(bp);
}
static void *coalesce(void *bp)
Handles the 4 cases that arise depending on whether the two
neighbors of the block to be freed are themselves free or allocated.
Frees the block pointed to by bp in constant time and returns
a pointer to the free block after it has been merged (coalesced)
with surrounding blocks if they were already free.
Here is the corresponding mm_free:
1 void mm_free(void *bp)
2 {
3 size_t size = GET_SIZE(HDRP(bp));
4
5 PUT(HDRP(bp), PACK(size, 0));
6 PUT(FTRP(bp), PACK(size, 0));
7 coalesce(bp);
8 }
The find_fit function in this initial version is the same as
the practic problem in
chapter 10:
static void *find_fit(size_t asize)
{
void *bp;
/* first fit search */
for (bp = heap_listp; GET_SIZE(HDRP(bp)) > 0; bp = NEXT_BLKP(bp)) {
if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp)))) {
return bp;
}
}
return NULL; /* no fit */
}
When the find_fit returns a pointer to a free block that is
big enough, it should be usually be split into the size
requested and the rest should be a smaller free block.
The size of the part allocated should be adjusted up if
smaller than some minimum size, and rounded so that the 8 byte
boundary alignment is satisfied.
In the "better" malloc.c file, the place function
handles the splitting of a block of size csize
in order to satisfy a request of size asize:
static void place(void *bp, size_t asize)
{
size_t csize = GET_SIZE(HDRP(bp));
if ((csize - asize) >= (DSIZE + OVERHEAD)) {
PUT(HDRP(bp), PACK(asize, 1));
PUT(FTRP(bp), PACK(asize, 1));
bp = NEXT_BLKP(bp);
PUT(HDRP(bp), PACK(csize-asize, 0));
PUT(FTRP(bp), PACK(csize-asize, 0));
}
else {
PUT(HDRP(bp), PACK(csize, 1));
PUT(FTRP(bp), PACK(csize, 1));
}
}
The Makefile will allow you to build an executable named
mdriver which is compiled with
- mdriver.c
- memlib.c
- mm.c (Your functions)
- several files for timing
Each time you modify mm.c, you should recompile mdriver:
make
Your heap management functions (mm_maloc, mm_free, and
mm_realloc) will be tested on 11 trace files
consisting of heap memory requests.
Some of these trace files come from actual programs, while
some have been artificially constructed.
Some the files are relatively large so rather having a copy
in every user's directory, they are located in the readable directory:
~glancast/374class/malloclab/traces
The makefile, Makefile, has a target named test, test01,
test02, ... test11.
To run tests, type
- Run all tests
make test
- Run only the 3rd test
make test03
- Run the n-th test (n = 01 to 11)
make testn
The malloc.c file contains a partial check that a pointer is
actually pointing to the "payload" portion of a block on the
heap.
static void checkblock(void *bp)
{
if ((size_t)bp % 8)
printf("Error: %p is not doubleword aligned\n", bp);
if (GET(HDRP(bp)) != GET(FTRP(bp)))
printf("Error: header does not match footer\n");
}
The void
mm_free(void *bp)
function should also check that the pointer bp is
actually pointing to the "payload" of an allocated
block on the heap before executing code to free it.
Otherwise, the heap could become corrupted!
The problem with the implicit free list is that searches for
a free block of the right size must examine allocated as well
as free blocks.
One improvement would be to have an explicit
list consisting of free blocks only.
So we should review options for constructing linked
lists.
- Each free block could contain the address of the next free
block - the "next" link. (singly linked list)
- Should we also have a "prev" link? (doubly linked list)
- How do we know if the list is empty?
- Should we have head node and/or and trailer node?
- Should the list be circular?
If a linked list is doubly linked, then deleting a
block pointed to by bp takes constant time:
after = NEXT_FREE(bp);
before = PREV_FREE(bp);
NEXT_FREE(before) = after;
PREV_FREE(after) = before;
We would need to write these two macros:
The code for deleting block from the free list above will not
work if the free list is empty! So check first.
However, the code make also not work at the two ends of the
heap or if there is only 1 free block left on the free list!
You could check for those special cases.
Adding special nodes (blocks) to mark the beginning and the
end of a linked list can make the code above work in all
cases.
With Header and Trailer "free" blocks, every actual free
block will have a previous block and a next block and
so the deletion code will work in all cases including the
problem boundary cases.
Instead of having both a Header and a Trailer Node, you can
get by with only a single Header Node if you make the list be
circular.
The last free block will have its next block be the Head
"free" block.
The Head node will have its previous block be the last free
block.
If the free list is empty, you still have a Head "free"
node.
For an empty free list, the Head "free" node will have its
hext and its prev equal to itself.
Using a pointer with value 0 (NULL pointer value) to access
memory will cause a segmentation fault.
If a doubly linked circular list with Header Node is used, no
links in the free blocks will ever be NULL.
To maintain the free blocks in an explicit free list, a
doubly linked circular list with header has advantages over
other choices. But...
- The Head Nodes need to be in the heap along with the free
blocks.
- Finding a suitable free block to use will only require
looking at free blocks, not allocated blocks.
- Deletion operations can be constant
time once the right free block to use has been found.
- Insertion of a block onto the free list can be constant
time, if it is simply inserted at the beginning of the
explicit free list.
First Fit and Next Fit are both easy to implement once the
linked list manipulation code is available.
Best Fit or worst fit require more coding and more time to
keep the free list sorted.
Alternatively some data structure other than a linked list
might be more efficient for best fit or worst fit.
Where are insertions into the free list?
LIFO
If insertions are made at the front, the time is constant
independent of the number of free blocks. Fast!
Recently freed blocks will be the first candidates to be reused.
Address Order
It is more work to keep the list of free blocks in order by
their addresses.
However, it is claimed that this results in less external
memory fragmentation.