Due 11:59 PM, Friday, October 28, 2016
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.
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.
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.
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.").
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.
The data structures are illustrated in the picture and text below.
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.
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.
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.
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.
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.
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
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: 2025-04-23 15:58:28 -0400 [validate xhtml]