CSC374 Feb04

slide version

single file version

Contents

  1. Simple Allocator Code
  2. Blocks
  3. Prolog Block
  4. Epilog Block
  5. Heap Management Library
  6. The mm.c Functions
  7. Functions to write/complete in mm.c
  8. Call Graph
  9. mm_malloc
  10. Better Initial Versions
  11. Better mm_malloc
  12. Auxilliary Functions
  13. Extend_heap
  14. Coalesce
  15. Better free
  16. find_fit
  17. Splitting
  18. Splitting: The place Function
  19. The mdriver Testing Program
  20. Running mdriver
  21. Check for Invalid Pointer
  22. Linked Lists
  23. mm_malloc: deletion a block from free list
  24. Header Nodes and Trailer Nodes
  25. Circular Lists with Header Node
  26. Null Pointers
  27. Linked Lists: Summary
  28. First Fit/Next Fit
  29. Insertions into Free List

Simple Allocator Code[1] [top]

The memory model using an implicit free list consists of

Prolog
block
  Epilog
block
hd ft hd free ft hd alloc ft hd alloc ft hd free ft hd ft

Blocks[2] [top]

All blocks have a 4 byte header and a 4 byte footer

hd   ft

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.

Prolog Block[3] [top]

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.

Epilog Block[4] [top]

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.

Heap Management Library[5] [top]

The functions in the memlib.c file for managing the heap:

You should not change anything in these functions.

The mm.c Functions[6] [top]

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) */
    ""
};
      

Functions to write/complete in mm.c[7] [top]

but mm_init is called first and then these call other functions. You may also need to add some functions and some macros!

Call Graph[8] [top]

mm_malloc[9] [top]

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)
{
}

Better Initial Versions[10] [top]

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.

Better mm_malloc[11] [top]

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	}

Auxilliary Functions[12] [top]

The file mm.c with the "better" version of mm_malloc also contans some auxilliary functions you can use including:

Extend_heap[13] [top]

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);
}

Coalesce[14] [top]

 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.

Better free[15] [top]

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	}

find_fit[16] [top]

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 */
}

Splitting[17] [top]

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.

Splitting: The place Function[18] [top]

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 mdriver Testing Program[19] [top]

The Makefile will allow you to build an executable named mdriver which is compiled with

Each time you modify mm.c, you should recompile mdriver:

	make 
      

Running mdriver[20] [top]

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
         
      

Check for Invalid Pointer[21] [top]

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!

Linked Lists[22] [top]

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.

mm_malloc: deletion a block from free list[23] [top]

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:

Header Nodes and Trailer Nodes[24] [top]

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.

Circular Lists with Header Node[25] [top]

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.

Null Pointers[26] [top]

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.

Linked Lists: Summary[27] [top]

To maintain the free blocks in an explicit free list, a doubly linked circular list with header has advantages over other choices. But...

First Fit/Next Fit[28] [top]

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.

Insertions into Free List[29] [top]

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.