CSC374 Feb12

slide version

single file version

Contents

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

Memory Management Before Virtual Memory[1] [top]

Memory Allocation/Deallocation[2] [top]

Programs must therefore be loaded into contiguous memory blocks large enough to hold the entire program.

For an operating system embedded in a single purpose device with no external storage, this may be ok.

In a multiprogramming operating system, many processes will be loaded into memory.

Memory allocation occurs when a process is created and deallocation occurs when the process terminates.

However, the order of deallocation is not predictable from the order of allocation!

Memory Management Data Structures[3] [top]

So in general, there must be a free list of the memory blocks not in use and available for allocation.

This list needs to keep track of the beginning address of each free memory block and the size of the block.

Memory allocation will select a free memory block (or part of one) which is big enough for the memory request and remove the block (or part of it) from the free list.

Process termination requires returning the memory to the free list.

Internal Fragmentation[4] [top]

Internal fragmentation is memory that is allocated to a process but not used (for data or code).

This may occur if memory is allocated in minimum units such as multiples of 4K bytes. A program that requries 150K + 1 bytes would be allocated 154K bytes. Of this 4095 bytes (4k - 1 bytes) would be internal fragmentation.

Internal fragmentation represents some inefficiency in the use of memory.

External Fragmentation[5] [top]

External fragmentation is free memory that is broken into contiguous memory blocks so small that they cannot contain any program and so cannot be allocated.

Memory Allocation Algorithms[6] [top]

Three typical memory allocation algorithms are:

        First Fit  Memory blocks are ordered. 
                   Choose the first block large enough.

        Best Fit   Choose the smallest free block which is large
                   enough.

        Worst Fit  Choose the largest block.
    

In all cases, if the chosen free block is not the exact size, split off the amount needed, leaving a smaller free block of the extra amount. This eliminates all internal fragmentation.

Unfortunately, these allocation strategies cannot avoid external fragmentation.

Example[7] [top]

A memory management system is working with a total memory size of 700K and uses contiguous allocation and variable sized partitions so that no internal fragmentation occurs. It currently has three free blocks available. Their sizes are 50K, 155K, and 100K, respectively, and their location in the memory is as below. The order of the free list is just the increasing order of the beginning addresses of the free blocks.

0      --- increasing addresses -->          700K
+-------+-----+------+------+------+------+------+
| used  | 50K | used | 155K | used | 100K | used |
+-------+-----+------+------+------+------+------+
      
RequestFirst-FitBest-Fit
 50,155,10050,155,100
9050,65,10050,155,10
10050,6550,55,10
6050, 5wait

At the third request (for 60K), both First Fit and Best Fit has a free list with a total size of 115K, more that enough. However, for Best Fit this 115K is broken (fragmented) into three pieces of size 50K, 55K, and 10K. None of these 3 blocks is big enough for the request. So the request must wait until some process terminates and releases more memory.

Summary of Memory Management (without Virtual Memory)[8] [top]

Memory must be allocated in contiguous memory blocks large enough to hold the entire program (or at least each entire segment - code, data, etc.)

Internal and external memory fragmentation can result. Each of these represents unused memory which limits the number and/or size of simultaneous processes.

Memory allocation algorithms that split off the exact memory request size from free blocks can eliminate internal fragmentation.

However, these algorithms (first fit, best fit, worst fit) all suffer from external fragmentation.

Simple Allocator Code[9] [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[10] [top]

All blocks have a 4 8 byte header and a 4 8 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 60 bits of the header/footer and the 3 4 lower bits of the size are implied and are 0. This means the block size is a multiple of 8 16.

The payload is the portion of the block between the header and footer.

The payload must begin at an 8 16 byte address boundary; that is, the payload address must be a multiple of 8 16.

Prolog Block[11] [top]

The prolog block has a header and a footer and an empty payload and so the size of the prolog block is 8 16.

The prolog block is marked as allocated.

So in binary, the header is

	0000 0000 0000 0000 .... 0000 0000 0001 0001
      

As an integer, this looks like 17, but the lower order 4 bits are not part of the size. The size has 4 implied (not stored) low order bits equal to 0:

	0000 0000 0000 0000 0000 0000 00010000
      

which is the value 16

It is convenient to write the header value as 16/1: size 16 and allocated bit = 1 or 16/0 for a free block of size 16..

Epilog Block[12] [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 (no footer), 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[13] [top]

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

You should not change anything in these functions.

The mm.c Functions[14] [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[15] [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[16] [top]

mm_malloc[17] [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[18] [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[19] [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[20] [top]

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

Extend_heap[21] [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[22] [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[23] [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[24] [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[25] [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[26] [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[27] [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[28] [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[29] [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[30] [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[31] [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[32] [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[33] [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[34] [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[35] [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[36] [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[37] [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.