The following diagrams show the internal state of the pool-based allocator. The arrows represent pointers.
After mpool_create
has been called, the state looks like this:
Observe:
alloc_list
points to an empty doubly-linked list (dbll).free_list
points to a dbll containing a single llnode
.user_data
of this llnode
points to an alloc_info
object that contains offset 0 and size equal to the pool size (X
). Essentially this indicates that a free block of at most size X
is available for allocation.NOTE: Not shown in this figure (and all other figures that follow) is that
start
points to a memory block obtained by malloc
that is X
bytes in size.
Now, consider the situation when the memory in the pool is exhausted.
Assume two allocations were made, of size a
and b
. If a + b = X
(i.e. pool size), then the pool is full. The internal state can be
represented as below:
Observe:
free_list
is now an empty list.alloc_list
contains two nodes, one for allocation of size a
and another for size b
alloc_info
nodes point out that the first block is at offset 0 (so its memory address is start + 0
, while the second block has address start + a
.Recall that pointers, by themselves, do not contain size information of the allocated memory block. That is why the memory allocator needs to track sizes using structures like alloc_info
.
Note that alignment constraints might lead to a situation when a block
of size a
was allocated for a request of size less than a
.
However, this is one design choice. It is possible to leave the
padding between allocations in the free list as tiny blocks.
Assume the pool has just been created, and you need to allocate a block of size a
:
To allocate a block of size a
, you scan the free_list
for a free
block that can larger than or equal to a
while respecting alignment
constraints. Once you have found it, you create a node for the
allocated block and place it in alloc_list
, and reduce the size of
the free block. Note, to handle alignment restrictions, you may need
to even split the free block. In our case, the first free block has
size X
, and offset 0
which can satisfy any alignment. That allows
us to just reduce the size of the free block assuming a
is strictly less than X
.
Consider, now, the steps to free the block of size a
in the previous example (i.e. memory at address start + 0
):
First, you will scan the alloc_list
to look for the block starting
at offset 0. Once you have found that block, you will remove it from the dbll.
The next step is to add this block back to the free list. If the most recently freed block is not immediately adjacent to any existing free block, you can just reinsert it back into the free list. However, the common case (which is true in this case as well) is that there could be blocks on the free list that can be combined (or coalesced) with the most recently freed block.
In our example, the recently freed block starting at offset 0 (and of
size a
) can be coalesced with the free block starting at offset
a
. This results in the following state (which is identical to our
initialization state):
If you have other questions, please ask them on Blackboard, but make sure you provide enough detail.