Due: 5:00pm, Friday, November 7. Value: 45 pts.
Your assignment is to complete two subroutines malloc
and free
using the technique described under
Memory allocation
below.
Such implementations would allow a program to use dynamic memory
allocation.
I am providing you with three files with which to start.
heap.s | The file that you should modify and submit for this assignment. It contains stubs for the subroutines you will write. You should not create or modify other files. |
test.s | The main file you would load into aas for
testing your subroutines of heap.s. The testing process
is described under Test codebelow. |
io-sub.s | Contains I/O subroutines. These are similar to those of the earlier
assignment, but these use the ARM calling convention seen in class. Also
included is the subroutine printHex . The test.s file
uses these subroutines. |
Using breakpoints is useful for this assignment. In particular,
you may want to enter break doTest
so that the program
pauses each time it begins another iteration of the testing process.
At that point, you may want to use list
to view memory;
list 800 840
displays the first 64 bytes in the
heap.
In this assignment we'll use a heap starting at memory address 0x800 and containing 512 (= 0x200) bytes.
Our heap will be a succession of blocks, some allocated and some free. Each block will have a length that is a multiple of four, and it will be preceded by a four-byte header specifying how long the block is, including the header. Note that since lengths are always a multiple of 4, the 1's and 2's bit of each block length will always be 0. Rather than always store a 0 in the 1's bit, though, we'll use the header's 1's bit to indicate whether the block is allocated (1) or available (0).
The malloc
routine works as follows. Given a desired
allocation length, it first computes the required block length by adding
4 bytes for the header and rounding up to the next multiple of 4. It
then starts at address 0x800 and steps through the blocks until it finds
an available block of at least the required length. If the block is
precisely of the correct length, it marks the block allocated. More
likely, the available block will be too long, and it will split the block
into two
pieces, of which the first is marked as the allocated block. In either
case, the address it returns is the address of the first byte following
the header.
The free
routine is given the address of the first
byte following a block's header. It clears the bit in the block's
header so that the block is marked available. Also, if the block
following the newly freed block is itself marked as unallocated,
the subroutine should merge the two unallocated blocks into one.
(We won't worry about whether to merge with the preceding block is
free, since there's no easy way to identify where it is.)
Below is an example of how the heap would be set up by these subroutines after initializing the heap, allocating 10 bytes (which returns 0x804), allocating 25 bytes (which returns 0x814), freeing the first allocation, and finally freeing the second allocation.
addr | initial | malloc(10) |
malloc(25) |
free(0x804) |
free(0x814) |
0x800 | 0x200 | 0x011 | 0x011 | 0x010 | 0x010 |
0x804 | 0 | 0 | 0 | 0 | 0 |
0x808 | 0 | 0 | 0 | 0 | 0 |
0x80c | 0 | 0 | 0 | 0 | 0 |
0x810 | 0 | 0x1f0 | 0x021 | 0x021 | 0x1f0 |
0x814 | 0 | 0 | 0 | 0 | 0 |
0x818 | 0 | 0 | 0 | 0 | 0 |
0x81c | 0 | 0 | 0 | 0 | 0 |
0x820 | 0 | 0 | 0 | 0 | 0 |
0x824 | 0 | 0 | 0 | 0 | 0 |
0x828 | 0 | 0 | 0 | 0 | 0 |
0x82c | 0 | 0 | 0 | 0 | 0 |
0x830 | 0 | 0 | 0x1d0 | 0x1d0 | 0 |
0x834 | 0 | 0 | 0 | 0 | 0 |
: | : | : | : | : | : |
(In the last column, we merged the blocks, and in the process I removed the header of the second block (at 0x830). This is not strictly necessary, and it does add a needless memory access. But I recommend doing so to help with debugging.)
Though it's not displayed in the above memory diagrams, the test
program actually writes into the first byte of each memory allocation.
The value stored in the byte is the index of the entry that
references the allocated block. The test program clears this byte before
calling free
.
The testing code, summarized by the C code below (though it is
actually written in ARM assembly), uses an array of 16 entries. The
program repeatedly selects a random entry from the array. If the entry
is zero, the program calls malloc
with a random parameter
between 1 and 32 and places the returned address into the array. And
if it is nonzero, it passes the value found into free
and clears the array entry to zero.
void *allocs[16];
int i;
int size;
for (i = 0; i < 16; i++) allocs[i] = NULL;
while (1) {
i = random() % 16; // random number from 0 to 15
if (allocs[i] == NULL) {
size = 1 + random() % 32; // random # bytes from 1 to 32
allocs[i] = malloc(size);
*((char*) allocs[i]) = (char) i; // mark first byte of allocation
printf("%x+%3x/%x\n", i, allocs[i], size);
} else {
printf("%x-%3x\n", i, allocs[i]);
*((char*) allocs[i]) = (char) 0; // clear first byte of allocation
free(allocs[i]);
allocs[i] = NULL;
}
}
The test code produces output as it goes, which you can use
for verifying that your implementations of malloc
and
free
work as they should. If the implementation is
correct,
you should see the below as the first fifty lines of output.
f+804/a 3+814/19 3-814 6+814/7 8+820/17 6-814 a+83c/1a a-83c 7+83c/f 9+850/1e 7-83c d+874/1b 1+814/5 9-850 a+83c/5 4+850/5 b+85c/9 5+894/11 b-85c b+8ac/16 f-804 4-850 7+804/b b-8ac c+850/13 2+8ac/17 4+8c8/1f b+848/3 d-874 9+874/14 9-874 5-894 5+874/9 d+894/f 0+8ec/11 0-8ec 6+8ec/d 9+900/15 f+91c/18 1-814 b-848 7-804 b+804/f f-91c 0+91c/10 e+930/13 0-91c 2-8ac f+868/7 1+884/7
Each line starts with the
index of the entry (0 to f) processed during the iteration,
followed by +
for a call to
malloc
and -
for a call to free
.
Next is the memory address returned by malloc
or
passed into free
. Finally, for lines corresponding to
malloc
, there is a slash and the length of the allocated block
in hexadecimal.