COMP 530: Lab 2: Malloc/Free

Due 11:59 PM, Friday, October 28, 2016

Introduction

In this homework, you will become familiar with how memory allocators, such as malloc and free work. You will implement a simple, single-threaded malloc and free, for small objects. This will be a real implementation, in that it will actually work as a drop-in replacement for the libc malloc and free implementations on classroom.cs.unc.edu (at least for some programs).

This assignment does not involve writing that many lines of code (probably hundreds), but the hard part is figuring out the few lines of delicate code to write, as well as writing careful unit tests for each step. We strongly recommend starting early and writing many test cases.

Getting started

We provided you with some "skeleton code" for the malloc and free implementation here. The baseline code defines all of the data structures you will need to implement malloc/free, as well as provides an outline of how you might implement these functions. Note that you may, of course, start from scratch or use any structure you like; however, you will probably find following this skeleton to be more productive.

To build the code, and a very, very simple test case, type make. You will get some warnings about undefined variables; these are hints for you. By the time you are finished, you shoudl not have warnings. The compiler will generate two binary files: th_alloc.so (the "Tar Heel Allocator", i.e., your malloc and free implementations), and test, a simple demo application.

To use your th_alloc.so library, we will use a directive called LD_PRELOAD, which basically steals spots in your application's dynamic linking jump tables before libc is loaded. The other functions will still use libc as normal. We provide test to illustrate how this should be used. It attempts to malloc one buffer, and then print the address of the buffer. In th_alloc.c, malloc is not implemented, and returns NULL. In libc, malloc returns a sensible buffer address.

porter@kermit:~/lab2$ ./test 
Hello 0x1dcb010
porter@kermit:~/lab2$ LD_PRELOAD=./th_alloc.so ./test 
Hello (nil)

In principle, th_alloc should be usable with any Linux application. In practice, once finished, it will work with a limited range of applications. In the interest of keeping the assignment simple, we are not going to support: multiple threads (there is a bogus stub for pthread_create to make any threaded application die immediately), allocations larger than 2K, and several variants of malloc that some applications use. Nonetheless, this lab should at least work with a wide range of test cases you write, as well as, some simple Linux applications. As a challenge problem, you are welcome to make this library more robust.

Helpful References

This skeleton code is a very simplified version of the design behind the Hoard memory allocator. We will read and discuss this paper (off-campus link) describing Hoard in class. Now is probably a good time to go ahead and read this paper; don't be discouraged if you don't understand all aspects of the paper just yet. But a general sense of their design will be useful in understanding the C code provided to you.

Debugging

Now is probably a very good time to familiarize yourself with gdb, or another C debugger. The problem with malloc and free is that they are a foundation for other common C libraries. For instance, printf may allocate memory to interpret a pattern. Where is this memory going to come from? And what will happen if printf calls malloc, calls printfs, calls malloc, ...?

For some comic relief during this assignment, Professor James Mickens has thoroughly-bemoaned this aspect of systems work in this article. (e.g., "I HAVE NO TOOLS BECAUSE I'VE DESTROYED MY TOOLS WITH MY TOOLS.").

Core assignment

You will first complete the implementation of malloc. We will begin by explaining some of the data structures provided to you for organization, and then sketch the procedure to follow.

Data Structures

The data structures are illustrated in the picture and text below.

malloc bookkeeping

Like Hoard, we will allocate objects from a superblock. In th_alloc, a superblock is one 4KB page, divided into equal-sized objects. The space for the first object in each page is reserved for some bookkeeping (see the struct superblock_bookkeeping). The rest of the space in the page will be kept on a free list (superblock->bkeep->free_list), until a call to malloc() removes it from the free list and returns it. A call to free() will return this object to the free list.

Each superblock only allocates objects of a given power of two. We will support object allocations up to 2KB; any allocation smaller than 32 bytes will be rounded up to 32 bytes. Any allocation that is not a power of two will be rounded up to the next-largest power of two.

Thus, we will have a pool of superblocks for each power of two, from 32 to 2048 (see the levels array in the code). Each level tracks the total number of free objects, how many superblocks have no allocated objects (for Exercise 4), and a pointer to a list of superblocks.

Initially, your allocator will have no superblocks allocated. So, a considerable part of your malloc implementation will involve populating the superblock pools, as well as traversing them

Note: This lab will require some clever use of casting and pointer math. This is fundamental to taking a blob of memory and turning it into subdivided, typed objects you are used to using. We have tried to give you some helper functions and boilerplate code for the nastier bits of this pointer-casting melee, but feel free to ask questions on piazza or during office hours if you do not understand the provided code.

Implementing Malloc

Exercise 1. (25 points) Complete the implementation of malloc. Write at least 5 test cases for malloc(), as well as however many additional unit tests within malloc you consider appropriate.

For the moment, you do not need to implement free().

The first step you need to do is complete the size2level function, which converts an allocation size to the correct power of two, and then maps it onto the correct index into the levels table. Important note: remember that level 0 is 32.

The second step will be to complete the alloc_super helper function. You will need to allocate one 4KB page of anonymous memory. We provide code to put this superblock on the superblock pool for the correct level, and initialize the free list. You will need to make sure the bookkeeping for the free object counts is correct, and, in general, make sure all of the bookkeeping fields are appropriately initialized.

We initialize the free list for you. Each object in the superblock is actually represented with a union of a singly-linked list pointer and a raw data pointer. What is going on here is that the free list is actually stored inside the free object, saving a bit of space. You will want to understand that code, so that you can complete the final step.

The final step is to complete the malloc implementation. Here, you will need to remove an entry from the free list, as well as decrement any appropriate counters. One important note: if you take the first object from an otherwise completely-free super block, decrement the whole_superblocks count. This will be useful for Exercise 4.

At this point, you should have a working malloc. We strongly recommend testing what you have up to this point thoroughly before moving on.

Implementing Free

Exercise 2. (20 points) Complete the implementation of free(), and write at least 5 additional tests (for a total of 10 test cases).

The main job of free() is to place an object back on the free list. We have, again provided a helper function to do the pointer math (obj2bkeep()) to map an object back to a superblock. The key idea is that we place the bookkeeping at the beginning of the page, so we just need to drop the "low order" bits to get the starting address of the page.

In addition to adding the object back to the free list, you will also need to be sure to update the statistics in the superblock and level about how many objects are free and how many superblocks have all objects free.

Memory Poisoning

An abundantly common source of errors have to do with dangling application pointers. These errors include using an object that has been freed, or using a field of an object without properly initializing it (inadvertently using an old value).

One common debugging technique to combat these types of errors is called memory poisoning. The basic idea is to fill newly-allocated, or newly-freed objects with a value that is unlikely to be mapped (other than 0). If this value is read, you will get a page fault; you can determine the type of bug by looking at the faulting address. If it looks like a poison value, you know you tried to follow a pointer in a freed data structure.

Exercise 3. (10 points) Implement memory poisoning in malloc and free, using the FREE_POISON and ALLOC_POISON patterns. Hint, check out memset(). These patterns should not just be for a single byte, but all bytes in the free area (except for the free list pointer).

Be sure to write some test cases for poisoning.

Returning Pages to the OS

Sometimes programs allocate many objects, then free many objects, but continue executing. In these cases, a well-behaved allocator will eventually return superblocks to the OS. Here, we define a number of free superblocks to keep (2, or RESERVE_SUPERBLOCK_THRESHOLD). Your job will be to extend free() to release superblocks if more entire superblocks are available than this threshold.

Exercise 4. (20 points) When more than 2 superblocks are completely free, release them back to the OS, using munmap, and update the appropriate bookkeeping in your allocator.

Be sure to write a test case or two that ensure that super blocks are actually being freed when appropriate, and that only free memory is being unmapped.

Challenge! (5 bonus points) Handle allocations larger than 4KB. Our advice would be to simply mmap the regions. The hard part is figuring out, on free, which regions are which, and unmapping on a large free.

Challenge! (5 bonus points) Handle allocations larger than 4KB. Our advice would be to simply mmap the regions. The hard part is figuring out, on free, which regions are which, and unmapping on a large free.

Challenge! (15 bonus points) Implement thread safety, by locking the relevant data structures during allocation and free. This will be very challenging, as we have not covered this in class. Consider reserving this challenge for after lab 3. In short, though, the tricky issue is that you may not want to use libpthread (to avoid circular dependencies), but write your own spinlock.

Challenge! (5+ bonus points) Implement additional allocator functions, such as calloc, realloc, and friends. Credit varies based on how many additional functions are implemented, how clean the implementations are, and how well-tested.

Coda

If your malloc implementation is robust, it should work to preload this with real applications. Many applications may allocate larger regions of memory, use unimplemented functions (like realloc), or may use threads. It is ok if this doesn't work for all applications, but you may want to try your mall with a few simple command-line utilities, just for the pride of accomplishment.

You can use the LD_PRELOAD directive on any command, such as:

$ LD_PRELOAD=./th_alloc.so ls

Hand-In Procedure

For all programming assignments you will "turn in" your program for grading by placing it in a special directory on a Department of Computer Science Linux machine and sending mail to the TA alias (comp530ta-f16 at cs dot unc dot edu). To ensure that we can grade your assignments in an efficient and timely fashion, please follow the following guidelines precisely. Failure to do so will potentially result in your assignment receiving a failing score for this assignment.

As before, create a directory named lab2 (inside your ~/comp530/submissions directory). Note that Linux file names are case sensitive and hence case matters!

When you have completed your assignment you should put your program and any other necessary parts (header files, etc.) in the specified subdirectory and send mail to the TA (comp530ta-f16 at cs dot unc dot edu), indicating that the program is ready for grading. Be sure to include "COMP 530" in the subject line. In this email, indicate if you worked alone or with a partner (and both team members' names and CS login names, as many of you have emails different from your CS login); be sure to cc your partner on this email. If you used any late hours, please also indicate both how many late hours you used on this assignment, and in total, so that we can agree.

After you send this email, do not change any of your files for this assignment after sending this mail (unless you are re-turning in late, as prescribed by the lateness policy).! If the timestamps on the files change you wi ll be penalized for turning in a late assignment. If your program has a timestamp after the due date it will be considered late. If you wish to keep fiddling with your program after you submit it, you should make a copy of your program and work on the copy and should not modify the original.

All programs will be tested on classroom.cs.unc.edu. All programs, unless otherwise specified, should be written to execute in the current working directory. Your correctness grade will be based solely on your program's performance on classroom.cs.unc.edu. Make sure your programs work on classroom!

Functional correctness will be based on your implementation's ability to handle a wide range of allocation and free patterns, not leaking memory, as well as how thorough your test cases are.

Your code should compile without any errors using the provided Makefile. The boilerplate code does include some warnings for unused variables; these are hints and should be used by the time you are done.

The program should be neatly formatted (i.e., easy to read) and well-documented. In general, 75% of your grade for a program will be for correctness, 25% for "programming style" (appropriate use of language features [constants, loops, conditionals, etc.], including variable/procedure/class names), and documentation (descriptions of functions, general comments [problem description, solution approach], use of invariants, pre-and post conditions where appropriate).

Make sure you put your name(s) in a header comment in every file you submit. Make sure you also put an Honor pledge in every file you submit.

This completes the lab.


Last updated: 2016-11-21 21:42:43 -0500 [validate xhtml]