Starting from:
$30

$24

Homework 3 Dynamic Memory Allocator Solution

We **HIGHLY** suggest that you read this entire document, the book chapter,

and examine the base code prior to beginning. If you do not read the entire

document before beginning, you may find yourself doing extra work.




:scream: Start early so that you have an adequate amount of time to test

your program!




:scream: The functions `malloc`, `free`, `realloc`, `memalign`, `calloc`,

etc., are **NOT ALLOWED** in your implementation. If any of these functions,

or any other function with similar functionality is found in your program,

you **will receive a <font color="red"ZERO</font**.




# Introduction




You must read **Chapter 9.9 Dynamic Memory Allocation** before

starting this assignment. This chapter contains all the theoretical

information needed to complete this assignment. Since the textbook has

sufficient information about the different design strategies and

implementation details of an allocator, this document will not cover this

information. Instead, it will refer you to the necessary sections and pages in

the textbook.




Takeaways




After completing this assignment, you will have a better understanding of:

* The inner workings of a dynamic memory allocator

* Memory padding and alignment

* Structs and linked lists in C

* [errno](https://linux.die.net/man/3/errno) numbers in C

* Unit testing in C




Overview




You will create a segregated free list allocator, adopting the "Buddy System"

as a memory management policy.




The allocation policy of this assignment is similar to that described in this

[wikipedia page](https://en.wikipedia.org/wiki/Buddy_memory_allocation).

You might find the step-by-step explanation there to be helpful.




In a buddy system, all blocks have sizes that are powers of two,

and each free list holds all the free blocks of one particular size.

A request for a block of memory of a particular size is satisfied by obtaining

a block from the free list that holds blocks just big enough to accomodate the

requested size (plus overhead for a block header).

If a block of a particular size is required, but there are currently no free

blocks of that size, then a free block of the next larger size is obtained,

it is split in half, and the two halves are inserted into the free list.

This process continues recursively, up to the largest supported block size.

If there are no free blocks of the largest size, then the operating system

is requested to increase the size of the heap to create a new free block.




When a block is freed under buddy memory allocation, an immediate attempt

at coalescing is made. Coalescing of a newly freed block is possible if and

only if the "buddy" of the block is already free. The buddy of a block is

an immediately adjacent block of the same size, whose address bears a special

relationship to that of its neighbor. In particular, the buddy of a block

having address `A` and size `S` is the block whose address is obtained as the

bitwise exclusive-OR `A^S` of the address `A` and size `S`. As the size

`S` is a power of two, this exclusive-OR amounts to toggling one bit of

the address `A`. For example, the buddy of the block with address `0xaa0200`

and size 512 (i.e. `0x200`) is the block with address `0xaa0000`, and vice versa.

These two blocks can be combined to yield a single block with address

`0xaa0000` and size 1024.

A block formed by coalescing two blocks is itself subjected to a coalescing

attempt. This continues until a block is obtained that cannot be coalesced

because its buddy is not currently free. The resulting block is added to

the free list appropriate for its size.




Using these basic ideas, you are to implement your own versions of the **malloc**,

**realloc**, and **free** functions. To avoid conflict with the system-defined

functions of the same names, the functions you will implement will be called

`bud_malloc`, `bud_realloc`, and `bud_free`. These functions are to satisfy

specifications given in more detail below.




To help debug and test your implementation, you can use some Criterion unit

tests that we have supplied. You will also write additional tests of your own

to verify that your implementation functions correctly.




# Detailed Description




As outlined above, in the buddy memory allocation technique, block sizes are

always powers of two. Because of this, we can describe the size of a block by

its **order**; that is, the logarithm to the base 2 of its size.

For example, a block of size 32 has order 5, a block of size 64 has order 6,

and so on.




The file `budmm.h` provided to you as part of the base code for this assignment

defines various constants pertaining to the allocator you are to implement.




```c

#define ORDER_MIN 5 /* Min block size 32 */

#define ORDER_MAX 15

#define ORDER_TO_BLOCK_SIZE(ord) (1 << (ord))

#define MIN_BLOCK_SIZE (ORDER_TO_BLOCK_SIZE(ORDER_MIN))

#define MAX_BLOCK_SIZE (ORDER_TO_BLOCK_SIZE(ORDER_MAX-1))

#define MAX_HEAP_SIZE (4 * ORDER_TO_BLOCK_SIZE(ORDER_MAX-1))

#define NUM_FREE_LIST (ORDER_MAX - ORDER_MIN)

```




The constant `ORDER_MIN` defines the order of the smallest block your allocator

should handle.

The constant `ORDER_MAX` has a value that is one greater than the order of the

largest block that your allocator should handle.

The difference `ORDER_MAX - ORDER_MIN` is the number of different block size

classes that your allocator will support. Since each block class has its own

free list, this is also the number of free lists, and a constant `NUM_FREE_LIST`

has been defined for that.

The macro `ORDER_TO_BLOCK_SIZE` maps an order to the corresponding block size.

The constant `MAX_HEAP_SIZE` defines the maximum size to which your heap can grow.

Note that you are not to make any changes to the `budmm.h` header file or the

`budutil.c` source file. In addition, your code should not make any assumptions

about the specific values of the constants defined in `budmm.h`.

We may decide, for example, to change the values of `ORDER_MIN`, `ORDER_MAX`,

and `MAX_HEAP_SIZE` when testing your code.




The header file `budmm.h` also defines two structures that are used for maintaining

information about memory blocks: `bud_header` and `bud_free_block`.

The `bud_header` structure is defined as follows:




```c

/* Struct for a block header (64 bits) */

typedef struct {

uint64_t allocated : 1;

uint64_t order : 4;

uint64_t padded : 1;

uint64_t unused1 : 10;

uint64_t rsize : 16;

uint64_t unused2 : 32;

} __attribute__((packed)) bud_header;




/*

Format of an allocated memory block

+---------------------------------------------------------------------------------------+

| 64-bits wide |

+---------------------------------------------------------------------------------------+




+-------------------------------------------+----------------+-------+------+-----+-----+ <- header

| unused | requested_size |unused |padded|order|alloc|

| | | | 1 | 4 | 1 |

| 32 bits | 16 bits |10 bits| bits | bits| bit |

+-------------------------------------------+----------------+-------+------+-----+-----+

| |

| Payload and Padding |

| |

+---------------------------------------------------------------------------------------+

*/

```




The fields of this structure mostly record information about a block of memory

that is needed whether or not the block is allocated or free.

The `allocated` field records the allocation status (1 = allocated, 0 = unallocated)

of the block. The `order` field records the order of the block.

The `padded` field records whether the block has been padded; i.e. whether

the requested size plus the size of the header is less than the actual

size of the block.

For an allocated block, the `rsize` field holds the requested size

(i.e. the size of the payload) for that block.




The `bud_free_block` structure is defined as follows:

```c

/* Struct for a free block */

typedef struct bud_free_block {

bud_header header;

struct bud_free_block *next;

struct bud_free_block *prev;

} bud_free_block;




/*

Format of a free block

+---------------------------------------------------------------------------------------+

| 64-bits wide |

+---------------------------------------------------------------------------------------+




+-------------------------------------------+----------------+-------+------+-----+-----+ <- header

| unused | requested_size |unused |padded|order|alloc|

| | | | 1 | 4 | 1 |

| 32 bits | 16 bits |10 bits| bits | bits| bit |

+-------------------------------------------+----------------+-------+------+-----+-----+

| next pointer |

| |

| 64 bits |

+---------------------------------------------------------------------------------------+

| prev pointer |

| |

| 64 bits |

+---------------------------------------------------------------------------------------+

| |

| FREE AREA |

| |

+---------------------------------------------------------------------------------------+

*/

```




This structure defines information about a block that is currently free.

Besides the header, there are `next` and `prev` pointer fields that are

used to link the block into a doubly linked free list, as described in

more detail below.




Note that the `bud_header` or `bud_free_block` structure will always be

stored at the beginning of a block. A pointer to the beginning of a block

can be cast either to type `bud_header *` or `bud_free_block *` to allow

access to the fields of either structure, depending on whether the block

is currently allocated or free.




The array `free_list_heads` contains the heads of the free lists for

the allocator. There are `NUM_FREE_LIST` entries in this array, one

for each order in the range `[ORDER_MIN, ORDER_MAX)`.

Each free list should contain only blocks of one particular order;

e.g. the list headed by `free_list_heads[0]` will contain only free

blocks of order `ORDER_MIN` (32 bytes), and the list headed by

`free_list_heads[3]` will contain only free blocks of order

`ORDER_MIN+3` (256 bytees).




For maintaining the free lists, you are to use *circular, doubly linked lists*,

with the entries of the `free_list_heads` array serving as *sentinels*

(google "circular doubly linked lists with sentinel" if you need an introduction

to this data structure).

The sentinel is a special node that serves to mark both the beginning

and the end of the list. It is never removed.

An empty list contains only of the sentinel node, whose `next` and `prev`

pointers refer back to itself.

The function `bud_mem_init` in `bud_util.c` initializes the free lists

in this condition.

Inserting into and deleting from a circular doubly linked list is done

in the usual way, except that, owing to the use of the sentinel, there

are no edge cases for inserting or removing at the beginning or the end

of the list.




:scream: You **MUST** use the entries of the `free_list_heads` array

as the sentinel nodes for the free lists, and you **MUST** implement

these lists as circular, doubly linked lists.

The helper functions discussed later, as well as the unit tests,

assume that you have done this when accessing your free lists.




For our purposes, all insertions and deletions from a free list will be

done at the beginning of the list (resulting in a LIFO discipline).

So inserting into a list would set the `next` field of the sentinel to be

the inserted node, and removing from a list would likewise modify the

`next` field of the sentinel.




The header `budmm.h` also contains function prototypes for several

utility functions that have been provided to you in `budutil.c`.




:scream: <font color="red" You must not modify anything in `budutil.c`.</font

However, it is encouraged to read through the functions in this file

to understand how the heap gets extended.




The provided utility functions are:




```c

/*

* Any program using the budmm library must call this function ONCE

* before issuing any allocation requests. This function DOES NOT

* allocate any space to your allocator.

*/

void bud_mem_init();

```




You must call `bud_mem_init` before doing anything else.

This function initializes the free lists and some internal state

variables, but it does not allocate any space to your heap.

In order to get space in the heap, you will need to use the

`bud_sbrk` function described below.




```c

/*

* Any program using the budmm library must call this function ONCE

* after all allocation requests are complete. If implemented cleanly,

* your program should have no memory leaks in valgrind after this function

* is called.

*/

void bud_mem_fini();

```




As the comment indicates, you must call this function once before your

program terminates.




```c

/*

* This function changes the position of your program's break.

* Each time it is called, the break pointer is increased by BLOCK_SIZE_MAX.

*

* @return On success, this function returns the previous program break.

* If the break was increased, this value is a pointer to the start of the newly

* allocated memory. On error, (void *)-1 is returned and errno is set to ENOMEM.

*/

void *bud_sbrk();

```




The `bud_sbrk` function is used to increase the size of the heap by moving the

heap break pointer. For this assignment, your implementation **MUST ONLY** use

`bud_sbrk` to do this. *DO NOT* attempt to use any system calls such as **brk**

or **sbrk** to move the break pointer.




:smile: A real allocator may use the **brk**/**sbrk** syscalls for small

memory allocations and **mmap**/**munmap** syscalls for large allocations. To

allow your program to use other functions provided by glibc, that rely on

glibc's allocator, we prove a safe wrapper around **sbrk**. This makes it so

that the heap breakpoint does not get altered by glibc's allocator and

destroy the memory managed by your allocator.




Each time `bud_sbrk` is called, the break pointer is advanced by `MAX_BLOCK_SIZE`,

which is the maximum size of a block that your allocator can handle,

until the total size of the heap has reached `MAX_HEAP_SIZE`, at which point

further calls to `bud_sbrk` will fail and return `(void *)-1`.

A successful call to `bud_sbrk` returns the old break pointer, which is a pointer

to the newly created block.




The `bud_sbrk` function keeps track of the starting and ending addresses of

the heap for you. You can get these addresses through the `get_heap_start` and

`get_heap_end` functions:




```c

/*

* @return The starting address of the heap for your allocator.

*/

void *get_heap_start();




/*

* @return The ending address of the heap for your allocator.

*/

void *get_heap_end();

```




Note that, although the address returned by `get_heap_start` is a valid

address in the heap, namely the first address in the heap, the address returned

by `get_heap_end` is the the first address *beyond the end* of the current heap,

which is not a valid address in the heap. This way of defining a memory region

is usually the most convenient way to do it.




The remaining function prototypes in `budmm.h` are for the functions you are to write.

You will implement the following three functions in the file `src/budmm.c`.

The file `include/budmm.h` contains the prototypes and documentation found here.




:scream: <font color="red"Make sure these functions have these exact names

and arguments. They must also appear in the correct file. If you do not name

the functions correctly with the correct arguments, your program will not

compile when we test it. **YOU WILL GET A ZERO**</font




```c

/*

* This is your implementation of bud_malloc. It acquires uninitialized memory that

* is aligned and padded properly for the underlying system.

*

* @param size The number of bytes requested to be allocated.

*

* @return If successful, a pointer to a valid region of memory of the

* requested size is returned, else NULL is returned and errno as follows:

*

* If the size passed is invalid, errno is set to EINVAL.

* If the request cannot be satisfied, errno is set to ENOMEM.

*/

void *bud_malloc(uint32_t size);

```




Notes on `bud_malloc`




When implementing your `bud_malloc` function, first determine if the request size

is valid. The request size is invalid if it is 0 or it is greater than

`MAX_BLOCK_SIZE - sizeof(bud_header)`. In the case of an invalid size,

set `errno` to `EINVAL` and return `NULL`. For a valid size, you will return either

a valid pointer in case of success, or `NULL` in case the request could not be satified.

In the latter case, `errno` should be set to `ENOMEM`.




Once the request parameters have been validated, you should attempt to satisfy

the request with the smallest block size that accommodates the request

(taking into account the header overhead).

For example, a request of size 200 should be satisfied by a block of size 256.

A request of size 256 will have to be satisfied by a block of size 512, in order

to have room for the header. Note that there will be a unique matching block size

for any valid request size. If there do not currently exist any free blocks of

the appropriate size, then the request should be satisfied by obtaining a free block

of the next higher size, splitting it in half, placing the half with the higher

address in the free list and using the half with the lower address to satisfy the

request. Note that if there are no free blocks of the next higher size, then

blocks of the next higher size after that should be considered, and so on,

up to blocks of size MAX_BLOCK_SIZE. Each time you use a larger block

to satisfy a smaller request, the larger block will be split in half and the

unused half will be left in the appropriate free list.

If there are no blocks in any free list up to size `MAX_BLOCK_SIZE`, then you should

call `bud_sbrk` to attempt to extend the heap and obtain a new block of size

`MAX_BLOCK_SIZE`.

If this fails, then you must set `errno` to `ENOMEM` and return `NULL`.




The pointer returned by `bud_malloc` is to the *payload area* of the

newly allocated block, which starts immediately after the header.




```

/*

* Marks a dynamically allocated region as no longer in use.

* Adds the newly freed block to the free list.

*

* @param ptr Address of memory returned by the function bud_malloc.

*

* If bud_free is called with an invalid ptr, it should call abort()

* to exit the program.

*/

void bud_free(void *ptr);

```




Notes on `bud_free`




When implementing `bud_free`, you must first verify that the pointer being

passed to your function belongs to an allocated block. This can be done by

examining the pointer address itself and the fields in the pointed-at header.

For this assignment, a pointer is to be regarded as invalid if any of the

following hold:




- The pointer does not correspond to an address in the range defined by

the return values of `bud_heap_start()` and `bud_heap_end()`

- The pointer is not aligned to a multiple of 8.

- The value in the `order` field does not lie in the range `[ORDER_MIN, ORDER_MAX)`

- The `allocated` bit in the header is 0

- The `padded` bit in the header is 0,

but `requested_size + sizeof(bud_header) != (block size)`

- The `padded` bit in the header is 1,

but `requested_size + sizeof(bud_header) == (block size)`

- The `requested_size` and `order` don't make sense; e.g.

`requested_size` is 100, but `order` is 6 (it is supposed to be 7)




If an invalid pointer is passed to your function, you must call `abort` to exit

the program. Use the man page for the `abort` function to learn more about this.




After confirming that a valid pointer was given, you must free the block.

Also, you should check if the newly freed block can be coalesced with its buddy.

See the [Immediate Coalescing](#immediate-coalescing) section for more details.




```c

/*

* Resizes the memory pointed to by ptr to size bytes.

*

* @param ptr Address of the memory region to resize.

* @param size The minimum size to resize the memory to.

*

* @return If successful, a pointer to a valid region of memory is

* returned, else NULL is returned and errno is set appropriately.

*

* A call to bud_realloc with a ptr == NULL should be equivalent to a

* call to bud_malloc(size).

* A call to bud_realloc with size == 0 should be equivalent to a

* call to bud_free(ptr).

* If bud_realloc is called with an invalid ptr it should call abort()

* to exit the program.

* If there is no memory available bud_realloc should set errno to ENOMEM.

*/

void *bud_realloc(void *ptr, uint32_t size);

```




Notes on `bud_realloc`




When implementing your `bud_realloc` function, you must first verify that the

pointer and size parameters passed to your function are valid.

If the size is 0, free the block and return `NULL`.

If a null pointer is passed, then `bud_realloc` should behave like a call

to `bud_malloc` of the same size.

Otherwise, the criteria for validity of the size and pointer are the same as

those described under [Notes on `bud_malloc`](#notes-on-bud_malloc)

and [Notes on `bud_free`](#notes-on-bud_free).




After verifying both parameters, you should determine the size of the new block

that best accomodates the requested size. If the calculated size is same as

the current block size, `bud_realloc` should do nothing and just return the

same pointer as was passed in.




If the calculated size is bigger than the existing block size, it is necessary

to allocate a new block. This should be done as for `bud_malloc`.

Once the new block has been allocated, the payload data should be copied

(using `memcpy`) from the old block to the new block and the old block should

be freed before returning a pointer to the payload area of the new block.




If the calculated size is smaller than the existing block size,

the pointer that is returned should be the same as the one that was passed as a

parameter, except that before doing so the block should be split in half if

it is at least twice as large as necessary. The unused half should be placed

on the appropriate free list and the order of the block should be reduced by

one. If the resulting block is still at least twice as large as necessary,

it should be split again, and so on, until the final block is the minimum size

that can accomodate the payload and header. All the unused portions of the

block should end up on the various free lists.




As mentioned in the [Splitting Blocks](#splitting-blocks) section,

the **left** buddy should be chosen for the next split target for each splitting step.

In the end, your allocator will select the leftmost child block for allocation;

this means that `bud_realloc` will return the same pointer as it was passed

as a parameter.




As for `bud_malloc` the pointer returned by `bud_realloc` is to the payload area

of the newly allocated block.




Block Size and Requested Size




The `requested size` of `bud_malloc` and `bud_realloc` is the number of bytes in memory

that the user wants to use. A certain range of requested sizes can be matched to the same

**actual** block size. e.g. for requested size of 150 and 200, they will both get the blocks

of the same size, 256 bytes. The `header` of each block occupies the first 8 bytes of each

block in this assignment.

Thus, you cannot allocate a 256-byte free block in response to a request of 256 bytes,

as there will be no room to store the header.

A 512-byte block would have to be allocated in this case.




Splitting Blocks




In practice, allocators typically split blocks to reduce the amount of internal fragmentation.

To satisty the buddy policy of this assignment, your allocator only ever splits a block

in half. For example, suppose that the requested size of `bud_malloc` is 50, but the only

free block that currently exists is a 256-byte block. The free block is split into two free blocks

of 128 bytes. Then, the **left** one of the split blocks (i.e. the one with the

lower-numbered address) is split again to become two 64 byte free blocks.

The **left** 64 byte free block then will be used for the allocation.

These steps of splitting blocks have resulted in one 64-byte allocated block,

one 64-byte free block, and one 128-byte free block. Of course, the free blocks

produced by each step are put into the proper free lists.




As in the example above, you should always select the **left** buddy for a target of

next split or allocation.




Refer to the textbook for a general explanation of splitting blocks.




Immediate Coalescing




Coalescing must **only** be done when `bud_free` is called, or in some cases when

`bud_realloc` is called.

According to the buddy policy, you should only coalesce the two free blocks

when they are **buddy**. As stated above, blocks of size `S` at addresses

`A` and `B` are buddy exactly when `A^S = B` (equivalently `B^S = A`).

Remember that the buddy of a block must be of the same size.

If the two have different sizes, they are not buddy.




Refer to the textbook for a general explanation of coalescing.




# Getting Started




Fetch and merge the base code for `hw3` as described in `hw0` from the

following link: https://gitlab02.cs.stonybrook.edu/cse320/hw3




**Remember to use the `--strategy-option theirs` flag with the `git merge`

command as described in the `hw1` doc to avoid merge conflicts in the Gitlab

CI file.**




Directory Structure




<pre

.

├── .gitlab-ci.yml

└── hw3

├── include

│   ├── budmm.h

│   ├── budprint.h

│   └── debug.h

├── Makefile

├── src

│   ├── budmm.c

│   ├── budprint.c

│   ├── budutil.c

│   └── main.c

└── tests

└── budmm_tests.c

</pre




The program in `src/main.c` contains a basic example of using the initialization

and allocation functions together. Running `make` will create a `budmm`

executable in the `bin` directory. This can be run using the command `bin/budmm`.




The code in `src/budprint.c` (with corresponding header file `include/budprint.h`)

contains some print functions that you might find useful while debugging your code.

As these functions directly call `fprintf`, rather than using the `debug` macros,

you should not leave any calls to these functions in the final code that you submit.




The provided `Makefile` creates object files from the `.c` files in the `src`

directory, places the object files inside the `build` directory, and then links

the object files together to make executables, located in the `bin` directory.




Any functions other than `bud_malloc`, `bud_free`, and `bud_realloc` **WILL NOT**

be graded.




**DO NOT modify `budmm.h`, `budutil.c`, or the Makefile.** Both will be replaced when we run

tests for grading. If you wish to add things to a header file, please create

a new header file in the `include` folder.




# Unit Testing




For this assignment, we will use Criterion to test your allocator. We have

provided a basic set of test cases and you will have to write your own as well.

You will use the Criterion framework alongside the provided helper functions to

ensure your allocator works exactly as specified.




In the `tests/budmm_tests.c` file, there are nine unit test examples. These tests

check for the correctness of `bud_malloc`, `bud_realloc`, and `bud_free`.

We provide some basic assertions, but by no means are they exhaustive.

It is your job to ensure that your header bits are set correctly and that blocks are

allocated/freed as specified.




Compiling and Running Tests




When you compile your program with `make`, a `budmm_tests` executable will be

created in the `bin` folder alongside the `main` executable. This can be run

with `bin/budmm_tests`. To obtain more information about each test run, you can

use the verbose print option: `bin/budmm_tests --verbose=0`.




Writing Criterion Tests




The first test `easy_malloc_a_pointer` tests `bud_malloc`. It allocates space

for an integer and assigns a value to that space. It then runs an assertion to make

sure that the space returned by `bud_malloc` was properly assigned.




```c

cr_assert(*x == &a, "bud_malloc failed to give proper space for a pointer!");

```




The string after the assertion only gets printed to the screen if the assertion

failed (i.e. `*x != 4`). However, if there is a problem before the assertion,

such as a SEGFAULT, the unit test will print the error to the screen and

continue to run the rest of the unit tests.




Your Additional Tests




For this assignment **you must write 5 additional unit tests

which test new functionality** and add them to `budmm_tests.c`

below the following comment:




```

//

//STUDENT UNIT TESTS SHOULD BE WRITTEN BELOW

//DO NOT DELETE THESE COMMENTS

//

```




For additional information on Criterion library, take a look at the official

documentation located [here](http://criterion.readthedocs.io/en/master/)! This

documentation is VERY GOOD.




# Hand-in instructions

Make sure your directory tree looks like this and that your homework compiles.




<pre

.

├── .gitlab-ci.yml

└── hw3

├── include

│   ├── budmm.h

│   ├── budprint.h

│   └── debug.h

├── Makefile

├── src

│   ├── budmm.c

│   ├── budprint.c

│   ├── budutil.c

│   └── main.c

└── tests

└── budmm_tests.c

</pre




This homework's tag is: `hw3`




<pre

$ git submit hw3

</pre




:nerd: When writing your program try to comment as much as possible. Try to

stay consistent with your formatting. It is much easier for your TA and the

professor to help you if we can figure out what your code does quickly.

More products