Β§17.2Low-level Mechanisms

Part I OSTEP pp. 169–176 Β· ~8 min read

  • splitting
  • coalescing
  • allocation header

Before any policy questions β€” which chunk should serve a request? β€” come the mechanisms every allocator shares: cutting chunks, merging chunks, remembering sizes, and the neat trick of storing the free list in the very memory it tracks.

Splitting and Coalescing

Recall the 30-byte heap and its two-entry free list. A request bigger than 10 fails; a request for exactly 10 fits either chunk. But a request for 1 byte? The allocator performs splitting : pick a chunk, cut it in two, return the first piece, keep the rest listed. Serving that 1 byte from the second chunk:

Splitting: malloc(1) returns address 20; the list barely notices

beforehead→addr:0
len:10
β†’addr:20
len:10
β†’NULL
afterhead→addr:0
len:10
β†’addr:21
len:9
β†’NULL

The corollary mechanism runs the other way. Suppose the middle 10 bytes (the used ones) are freed. Add them to the list without thinking and the whole heap is free β€” but the list says three shards:

Coalescing: without it, a fully-free heap fails a 20-byte request

naïvehead→addr:10
len:10
β†’addr:0
len:10
β†’addr:20
len:10
β†’NULL
coalescedhead→addr:0
len:30
β†’NULL

Coalescing fixes it at free time: look at the addresses of the returning chunk and its neighbors; if they sit adjacent, merge them into one larger chunk. Large extents stay visible to the list, not just theoretically present.

Tracking The Size Of Allocated Regions

free(void *ptr) takes no size β€” so the library stashes one. Most allocators keep a small header in memory just before each handed-out chunk:

typedef struct {
    int size;
    int magic;
} header_t;

Figures 17.1 + 17.2: an allocated region plus its header β€” ptr = malloc(20)

size: 20magic: 1234567hptr β–Έthe header usedby the malloc librarythe 20 bytes returned to callerptr β–Έwhat the userthinks they own

On free(ptr), simple pointer arithmetic recovers the header:

void free(void *ptr) {
    header_t *hptr = (header_t *) ptr - 1;
    assert(hptr->magic == 1234567);   // integrity check
    // freed region size = hptr->size + sizeof(header_t)
    ...
}

Note the small but critical detail: the freed region’s size is the header plus the payload. So when a user requests N bytes, the library doesn’t hunt for a chunk of size N β€” it hunts for N + the header size. Request 100, consume 108.

Embedding A Free List

The list needs nodes; nodes need memory; and the one thing a memory allocator cannot do is call malloc() for its own bookkeeping. The escape: build the list inside the free space itself.

typedef struct __node_t {
    int              size;
    struct __node_t *next;
} node_t;

// mmap() returns a pointer to a chunk of free space
node_t *head = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
                    MAP_ANON|MAP_PRIVATE, -1, 0);
head->size   = 4096 - sizeof(node_t);
head->next   = NULL;

Each free chunk’s own first bytes describe it. Step through the book’s 4KB heap β€” init, three allocations, a free, a mess, and the merge that heals it:

Figures 17.3 β†’ 17.7: the life of a 4KB heap, with the free list embedded in the free space itself
[virtual address: 16KB = 16384]size: 4088 Β· next: 0 (NULL)16384headthe rest of the 4KB chunk (free)

1Initialize: one node, planted in the space it describes (Fig 17.3)

mmap() hands the library 4KB at virtual address 16KB. The library writes a node_t at the very start of that space: size 4088 (4096 minus the 8-byte node) and next = NULL. The free list IS the free space.

step 1 / 6

Growing The Heap

And if the heap simply runs out? The simplest answer: fail. Returning NULL is an honorable approach β€” you tried, and though you failed, you fought the good fight. Most traditional allocators, though, start small and ask the OS for more: the sbrk() negotiation from the segmentation chapter β€” the OS finds free physical pages, maps them into the address space, and returns the new end of the heap. A bigger arena, same mechanisms.

Check yourself

1.The free list holds two 10-byte chunks (at 0 and 20). A request for ONE byte arrives, served from the second chunk. What does the allocator do, and what does the list look like after?

2.The middle 10 bytes of the 30-byte heap are freed, and the allocator naΓ―vely prepends the chunk to its list. The whole heap is now free β€” so why does a request for 20 bytes still fail?

3.How does free(ptr) discover the size of the region being freed β€” and what's the small-but-critical consequence for malloc(N)?

4.The free list needs nodes. Where does the memory for the list's own nodes come from?

5.In the 4KB walkthrough, the program frees the MIDDLE of three 100-byte chunks by calling free(16500). Why 16500 exactly?

5 questions