,glibcmalloc()free().
Learning Goals
The purpose of this program is to help you understand the nuances of building a memory allocator, to further increase your C programming skills by working a lot more with pointers and to start using Makefiles.
Specifications
For this assignment, you will be given the structure for a simple shared library that is used in place of the heap memory allocation functions malloc() and free(). You’ll code two functions in this library, named Mem_Alloc() and Mem_Free().
Memory Allocation Background
Memory allocators have two distinct tasks. First, the memory allocator asks the operating system to expand the heap portion of the process’s address space by using the sbrk() system call, but we’ll use mmap() to simulate a heap in the memory mapped area. Second, the memory allocator doles out this memory to the calling process that requests heap memory. This involves managing a list of free memory blocks. When the process requests memory the allocator searches that list for a free block that is large enough to satisfy the request and marks it as allocated. When the process later frees memory, the allocator marks that block as free.
This memory allocator is usually provided as part of a standard library rather than being part of the operating system. Thus, the memory allocator operates entirely within the virtual address space of a single process and knows nothing about which physical pages have been allocated to this process or the mapping from virtual addresses to physical addresses.
The C programming language defines its allocator with the functions malloc() and free() found in “stdlib.h” as follows:
- void *malloc(size_t s): allocates s bytes and returns a pointer to the allocated memory. The memory is not cleared.
- void free(void *ptr): frees the memory pointed to by ptr that was previously allocated by malloc() (or calloc() or realloc()). Otherwise, undefined behavior occurs as it also does or if an attempt is made to free an allocation more than once. If ptr is NULL, the function does nothing and simply returns.
Understand the Code
Copy the entire contents from following directory into your working directory for this assignment:
/p/course/cs354-skrentny/public/code/p3
In this directory you’ll find the files named “Makefile”, “mem.c” and “mem.h” as well as a directory named “tests”. In “mem.c” is the completed codefor two functions: Mem_Init(int sizeOfRegion) and Mem_Dump(). Look at these functions to understand what they do and how they do it. Also note the global block header pointer list_head is the head of our linked list of memory blocks, where each block would be marked as either free or allocated. Very carefully read the comments for the provided block header structure to understand the conventions used in this program. These functions we’ve completed are described below:
Mem_Init(int sizeOfRegion)
This function sets up and initializes the “heap” space that the allocator will manage. sizeOfRegion is the number of bytes desired for the heap.
This function should be called once at the start of any main program before calling any of the other allocator functions. In your main programs to test your code call this function first to initialize enough space so that subsequent calls to Mem_Alloc() function properly. The test main programs we’ve provided (discussed below) already do this.
Note that for improved performance, Mem_Init(int sizeOfRegion) rounds up the amount memory requested to the nearest page so it is possible that more memory might be allocated than originally specified by sizeOfRegion. All of this memory is initialized as the first and only block in the free list to be used by your allocator and is accessed via list_head, which will be pointing to that block’s header. This is the free list that your allocator uses to allocate blocks via Mem_Alloc() calls. Your allocator will need to divide this block into smaller pieces as memory is requested.
Mem_Dump()
This function is used for debugging. It prints the list of all the memory blocks (both free and allocated). Use this to determine if your code works properly. Implementing functions like this are very helpful and well worth your time when working on complex programs. It produces lots of useful information about the data structure so take a closer look at what it does.
Implement the Allocator
Note: Do not change the interface. Do not change anything within file “mem.h”. Do not change any part of functions Mem_Init() or Mem_Dump().
Write the code to implement Mem_Alloc() and Mem_Free(). Use best fit placement policy when allocating blocks with Mem_Alloc() and splitting the block if possible. When freeing memory, use immediate coalescing with the adjacent memory blocks if they’re free. Recall that list_head is the free list structure as defined and described in the file “mem.c”. It uses ideas as discussed in lecture (textbook in section 9.9.6) but adds a next pointer in the headers in order to make it easier to traverse through the free list. We’ll also use address ordering of the blocks in this list.
The functions you’ll code are described below:
void *Mem_Alloc(int size):
This function is similar to the C library function malloc(). It returns a pointer to the start of allocated payload of size bytes. The function returns NULL if there isn’t a free block large enough to satisfy the request. Note we won’t request more memory from the OS than what’s originally allocated by Mem_Init(). Mem_Alloc() returns single word (4 bytes) aligned chunks of memory, which improves performance. For example, a request for 1 byte of memory uses 4 bytes - 1 byte for the payload and 3 bytes for padding to achieve word alignment. To verify that you’re returning 4-byte aligned pointers, you can print the pointer in hexadecimal this way:
printf(quot;%08xquot;, ptr)
The last digit displayed should be a multiple of 4 (that is, 0, 4, 8, or C). For example, 0xb7b2c04c is okay, and 0xb7b2c043 indicates a problem with your alignment.
Once the best fitting free block is located we’ll split the block in two to minimize internal fragmentation. The first part becomes the allocated block, and the remainder becomes a new free block. If the remaining free block doesn’t meet the minimum size (the header plus its minimum payload of 4 bytes) then don’t split the block. Note, you are not to remove the final allocated block from the list of blocks.
int Mem_Free(void *ptr)
This function is similar to the C library function free(). It frees the block of heap memory containing ptr’s payload and returns 0 to indicate success. Before you free a block, you must check that ptr is indeed pointing to the payload of a previously allocated block. If it is not, then the function just returns -1 to indicate failure. If ptr is NULL the function just returns -1. When freeing a block you must coalesce it with its adjacent blocks that are free. Remember, this leaves only one header in the coalesced free block.
Test the Code
You have a provided a file, named “Makefile”, that is used to compile your code in “mem.c” and “mem.h” into a shared library named “libmem.so”. To compile, enter the command make on the Linux command line while you are working in your project directory.
Once the shared library is created, you can then test if your allocator works properly by writing a test main program. Your test program links in this shared library, and makes calls to the functions Mem_Alloc() and Mem_Free(). We’ve already written some small programs that do this, and to help you get started in writing your own tests. Our test programs are in the “tests” subdirectory that you copied along with a second Makefile used to compile those test using the same make command.
You’ll also find the file “testlist.txt” that contains a brief description of the tests we provided ordered by increasing difficulty. Please note that these tests are not exhaustive. They cover a good range of test cases, but there will be additional tests that we’ll use to grade your code. To compile these tests change your working directory to the “tests” subdirectory and enter make on the Linux command line. This will make executables for all the test programs in this directory and link them to the shared library that you’ve compiled beforehand.
Notes and Hints
- Keep in mind that the value of size_status excludes the space for the block header. Make use of sizeof(block_header) to set the appropriate size of requested block.
- Write and thoroughly test small helper functions for common bitwise operations and checks. For example, code a helper isFree() that checks the block header bit to see if a block is free, setFreed() to set that bit to the freed state, setAllocated() to set that bit to the allocated state.
- Double check your pointer arithmetic. (int*)+1 changes the address by 4, (void*)+1 and (char*)+1 changes it by 1. What does (block_header*)+1 change it by?
- For main programs that you write to test your allocator, make sure you call Mem_Init() first to allocate the initial space.
- Check return values for all function calls to make sure you don’t get unexpected behavior.
- Incrementally build up your program by testing against the tests in the order that they are listed in “testlist.txt”. For e.g, focus on making sure your best fit algorithm is correct before you implement coalescing.