Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

kbrk and vbrk

What happens if you don’t have enough space left? You’ve done some allocations but realise you need more room. Well, all you need is a brk.

The brk functions move the “break point” — the boundary between used and free memory. Everything below the break is in use, everything above is available. kbrk and vbrk move this boundary for the heap and vmalloc region respectively.

kbrk

The kbrk version needs to do a bit of searching as we need to walk the linked list to find the end of the heap. It’s relatively easy to do but we need to be careful not to move outside of valid memory space. We have this check to make sure addr is in bounds:

let addr_u32 = addr as u32;
if addr_u32 < HEAP_START || addr_u32 > HEAP_MAX {
  return -1;
}

Notice that HEAP_MAX there? I found that when using kbrk to shrink the heap, that the original value gets lost and cannot be restored. Therefore when the heap is initialed, we save this value so we can always go back to it.

HEAP_MAX = start + size;

Then calculating and updating the size is simple:

HEAP_SIZE = addr as u32 - HEAP_START;

Now we need to walk from the start of the heap until we find the last block, then update its size to reflect the new boundary:

let mut head = HEAP_START as *mut BlockHeader;
while (*head).next != 0 as *mut BlockHeader {
    head = (*head).next;
}
if (*head).free == 1 {
    (*head).size = addr_u32 - (head as u32 + HEADER_SIZE as u32);
    return 1;
}
return -1;

We check (*head).free == 1 before updating — if the last block is occupied, we can’t safely resize it without corrupting live data. In that case we return -1 and let the caller handle the failure.

vbrk

The vbrk version is much simpler. It still needs the same bounds checks to make sure we stay within the vmalloc region, but because we already track where the next valid address is with VMALLOC_NEXT_VADDR, we just update it directly — no linked list walk needed.

if addr.is_null() {
    return 0;
}
let addr_u32 = addr as u32;
if addr_u32 < VMALLOC_START || addr_u32 > VMALLOC_END {
    return -1;
}
VMALLOC_NEXT_VADDR = addr_u32;
return 1;

This is the payoff of the cursor design — because VMALLOC_NEXT_VADDR already represents the break point of the vmalloc region, moving it is a one-liner.

Testing

Both functions are testable via the brk command in NPS:

brk -k    // test kbrk
brk -v    // test vbrk

Each test prints the current break point, moves it by one page (4096 bytes), verifies the change, then restores it:

Current size: 3522560
kbrk(curr - 4096)
New size: 3518464
Restored: 3522560

The restore is the important part — it proves that HEAP_MAX correctly preserves the original upper bound even after shrinking. Without it, kbrk would reject the restore address as out of range since HEAP_START + HEAP_SIZE would already be pointing to the shrunk value.