CS170: Lab 0 - Helpful Hints from your Aunt Heloise
Occasionally, your Aunt Heloise (who has served up many a fine batch of
operating systems delight) will provide you with some of her insights,
tips, and cooking secrets so your your CS170 assignments can be as tasty as
you want them to be. For this lab, the cooking temperature that you use,
and the order in which you add your ingredients, makes a huge difference.
Cook too fast, and you'll burn it -- too slow, and your TAs will grow
hungry waiting for your submission. Here is how your Aunt Heloise whipped
up her latest batch of malloc(). You can do it too.
Start with InitMyMalloc()
The key to this lab (and most of the other labs) is not to try and code
everything at the beginning. Yes -- I know. The strategy of writing
everything first and then debugging might lead you to believe that you will
get more partial credit since you can always claim it was "almost working."
Unfortunately, your Aunt Heloise doesn't quite buy this argument in the same
way that the TAs won't buy it, your future boss won't buy it, and your
co-workers won't buy it when you are late with your part of a joint
project. In this class,
you will be much better off with working code than with a file full of C
programming that you've crammed in at the last minute.
Therefore, your best strategy is to try and get the program working in stages.
To do so, you are going to want to write modules and test routines
at the same time. Each time you add a feature you should also write a
function to test that feature. The program testmymalloc.c was written in this way. Each test
went in to exercise a different part of the code.
The first routine to write, then, is InitMyMalloc() and the
second one you should write is PrintMyMallocFreeList().
As mentioned in the lab write-up you will need to
define a data structure to describe each block you allocate and free. You can
use what ever data structure you like, but your Aunt thinks that
struct malloc_stc
{
struct malloc_stc *next;
struct malloc_stc *prev;
int size;
unsigned char *buffer;
};
is like a good diamond: simple, elegant, goes with everything, and valuable.
The buffer you will use for space must be declared as a global variable.
You will also probably want to declare a free list head pointer as a global
variable at the same time. Your
Aunt declared them as
static struct malloc_stc *MyFreeList;
static unsigned char MyBuff[MAX_MALLOC_SIZE];
The function InitMyMalloc() must do two things. It must
- initialize your buffer so that there is one, big block containing all
of the free storage you can allocate, and
- initialize the free list head pointer to point to this block
To make this go, you will need to do a little casting. Here is a code segment
that surely constitutes the biggest hint provided in the history of Western
Civilization.
MyFreeList = (struct malloc_stc *)MyBuff;
MyFreeList->next = NULL;
MyFreeList->prev = NULL;
MyFreeList->size = sizeof(MyBuff) - sizeof(struct malloc_stc);
MyFreeList->buffer = (unsigned char *)(MyBuff + sizeof(struct malloc_stc));
This is the code that makes up the body of your Aunt's InitMyMalloc().
Stop.
Before you do the cut and paste thing, take a deep breath and relax. The code
isn't going anywhere. Take a moment to consider this code carefully. It
isn't many lines, but you should not proceed unless
you understand exactly what each line does and why. If you don't
see why each assignment is made in the way it is, you will not have much
hope of completing the rest of the assignment. Draw yourself some pictures.
Why is MyFreeList->size set this way? You might wonder why the field
buffer is in the structure. It points to the first byte of storage
in the buffer usable by the caller of MyMalloc(). Why is it set this
way? What is going on with the casts?
If you don't understand these basics, what can you do? Certainly, you can ask
the TAs but that solution will probably not get you much past a working
version of InitMyMalloc() (which won't count for any points since it is
pretty much all here). Understanding how and why InitMyMalloc() is
written this way requires that you have a fair understanding of C programming.
If you do not, you should plan on brushing up on your C before you proceed.
Your aunt notes that your instructor has provided some lecture
notes on C to help reacquaint you with its subtle flavors and textures.
In addition,
there are many courses on-line, and hundreds, if not thousands of books
available. C will be absolutely essential for all of the assignments
in this class. No matter how good your Java or C++ skills are, for this class
you will need to know C at this level.
PrintMyFreeList()
Your next step is to write a print function that can print out all of the free
blocks in your free list. This function should be very simple to write. The
only thing you need to know is that the end of your doubly-linked list will be
signified by a NULL pointer. Yes -- your free list should be
doubly-linked.
Test It
Before going any further, you should write a test function. You can write it
from scratch. Use the code for simpletest1.c as an
example, if you like. Add your test routine to the makefile so that it builds properly. If you don't
understand how a makefile works, it is time to do a little bit more study on
your own. All reasonable systems use the make utility to manage
code construction. Your test code should call InitMyMalloc() and
PrintMyMalloc() and then exit. Build it, and run it. Is the output
what you expected?
Start working on MyMalloc()
Your next step should be to start writing the function MyMalloc().
Given the configuration of your free list after InitMyMalloc() has been
called, what are you going to have to do when a call to MyMalloc()
occurs? Read the lab write-up and look at the
discussion of splitting. Write only the part that splits the first buffer in
the list. Write a new test program and test only this function. Use
your PrintMyMalloc() to make sure your free list looks right after a
call to MyMalloc(). You should also test the case when a call to
MyMalloc() cannot be satisfied because the request is too large. Be
sure that all of your sizes are rounded up to the nearest multiple of 8.
Start on MyFree()
Once you are confident that you have this part of MyMalloc() working,
you will need to start on MyFree(). Don't worry about coalescing at
this point. Just make sure that a free block returned to you via a call from
is added in the right place on your free list. There are two tricky
things here. First, the pointer passed to you in MyFree() is not a
pointer to your struct malloc_stc but rather the pointer you handed
back from a successful call to MyMalloc(). If you use your Aunt's data
structure, that pointer is the pointer contained in the field buffer.
You will need to recover the pointer to the structure from this pointer.
Think carefully and look at how InitMyMalloc() works. If you
understand that routine, it is trivial. Next, you need to make sure that your
free list remains sorted in terms of the block addresses. Each call to
MyFree() then must find the right spot in the list (this is technically
an insertion sort).
Test It
Write yourself another test program and make sure you can free the space you
allocate. Use your print routine to make sure that the data structures
look right.
Test it Some More
At this point, you have written the code to allocate and then free the first
block. What if you do two allocates and then free the second? That should
put two blocks on your free list that are "next" to each other in your
MyBuff array. Write a test code to test this case.
Go Back to MyMalloc and do First Fit
Now, you have a way to make a free list with multiple blocks, but your version
of MyMalloc() doesn't yet implement "first fit." Change it to look for
the first block on the free list that is big enough to hold the requested
size. Make sure that you take care of two cases. The first case is when you
need to split the free block because there is some left over. The second case
is when the fit is "tight enough" so that there is not enough left over to
hold your bookkeeping structures.
Test It
Write several more test codes. At this point you should have everything
working except coalescing. Make sure that you handle the failure cases
correctly too, especially with respect to fragmentation. Feel free to use the
programs simpletest1.c and simpletest2.c directly and
as the basis for your own test codes. Also, your Aunt has developed a
particularly tasty test code specifically for the non-coalescing case. It can
be found in testnocompact.c. Try this one as
well.
Coalescing
Now you should write the coalescing code. Write and test it incrementally in
the same way you did the other codes. Coalesce one block -- then two. Make
sure you can coalesce the entire list. The easiest way to make sure you are
testing correctly is to do your coalescing just before MyFree()
exits. In addition to your test codes, try your Aunt Heloise's version of
testmymalloc.c and see if it works too.
And that's it. You should now have a firm grasp of how malloc() works. Ask
yourself some questions. What happens if you malloc() a buffer that is 30
bytes long and then write 40 bytes into it? What happens to the extra ten
bytes? What happens if you forget to free memory?