Explore how operating system kernels track, partition, and coalesce variable-sized blocks of contiguous physical memory using bitmaps, linked lists, fit algorithms, and the binary buddy system.
When an operating system supports dynamic partitioning, the kernel must maintain an accurate bookkeeping model of which regions of physical memory are actively allocated to processes and which regions are available as free holes.
Under a bitmap representation, memory is divided into fixed allocation units (typically 4 bytes to several kilobytes). Each allocation unit corresponds to a single bit in a contiguous kernel bitmap array:
0: The corresponding unit is free.1: The corresponding unit is occupied by an allocated process.If $U = 4\text{ bytes}$ (32 bits), the bitmap consumes $\frac{1}{32} \approx 3.125\%$ of total RAM. If $U = 4\text{ KB}$, the bitmap consumes only $\frac{1}{32768} \approx 0.003\%$.
Defect: When an application requests a contiguous block of $k$ units, the kernel must scan the bitmap for a contiguous run of $k$ consecutive zero bits. Searching a bitmap for arbitrary-length bit runs is computationally slow ($\mathcal{O}(N)$ sequential bit-testing operations).
A more responsive design maintains a sorted, doubly linked list of allocated memory segments and free holes. Each node contains a descriptor tuple: $$ (\text{Type}, \text{Start Address}, \text{Length}, \text{Next Pointer}, \text{Prev Pointer}) $$ where $\text{Type} \in \{\text{P (Process)}, \text{H (Hole)}\}$.
Advantage: Free holes are explicit entities that can be traversed directly. When a process terminates, updating the list and coalescing adjacent holes is a constant-time $\mathcal{O}(1)$ pointer operation.
When a process requests $K$ bytes of contiguous memory, the kernel scans the free list to select an appropriate hole. Five classic search heuristics govern this decision:
Scans the free list from the beginning and allocates the very first hole that is $\ge K$. The hole is split: the process takes what it needs, and the remainder stays on the list as a smaller hole.
Functions like First-Fit, but maintains a roving pointer. It begins searching from the location of the previous successful allocation, wrapping around to the start if needed.
Searches the entire list from start to finish to find the smallest hole that is $\ge K$. It attempts to minimize the leftover residual space.
Always allocates the largest available hole in memory, with the intuition that splitting a large hole leaves behind a substantial remainder that remains useful.
When an active process terminates and relinquishes its memory block, the kernel must merge adjacent free holes to counter external fragmentation. There are exactly four boundary conditions that can arise when block $X$ is freed:
To determine whether the adjacent neighbor in physical memory is a hole without searching the entire linked list from the head, Donald Knuth introduced boundary tags. Every memory block contains a header and a footer storing its allocation status and size:
struct BlockHeader {
unsigned int is_allocated : 1;
unsigned int block_size : 31;
};
struct BlockFooter {
unsigned int is_allocated : 1;
unsigned int block_size : 31;
};
By examining the footer immediately preceding the block's start address, and the header immediately following the block's end address, the kernel performs neighbor inspection and hole coalescing in $\mathcal{O}(1)$ constant time.
While linked lists offer flexibility, finding holes and splitting arbitrary sizes can cause memory fragmentation. The Binary Buddy System (developed by Harry Markowitz in 1963 and refined by Kenneth Knowlton) is an elegant compromise that provides fast allocation and rapid coalescing.
Memory is allocated strictly in units that are powers of two: $2^U, 2^{U+1}, \dots, 2^L$.
When block $B$ is released, the allocator computes its buddy's address in one clock cycle. If the buddy is also marked free and has size $2^k$, the two buddies immediately merge back into a single block of size $2^{k+1}$. This coalescing cascades up the power-of-two hierarchy until an occupied buddy is reached.
Trade-off: The buddy system eliminates external fragmentation and coalesces in $\mathcal{O}(1)$ time, but suffers from moderate internal fragmentation because payloads are rounded up to powers of two.