Master the fundamental concepts of memory management through this focused micro-challenge.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksSlab allocation caches same-sized kernel objects (dentries, inodes) to avoid repeated buddy splits. Linux keeps per-type slabs with embedded free lists carved from contiguous pages.
Each slab tracks:
For example, allocating 128 byte struct file objects from a 4 KB page yields 32 slots per slab with one page fault instead of 32 buddy round trips.
Bonwick's 1994 slab allocator design became Linux's SLAB and SLUB subsystems, where kmem_cache pools serve task_struct, inode, and dentry objects on every fork() and file open across a running kernel. Because slabs reuse memory without re-zeroing it, slab corruption bugs are also a favorite target for kernel exploit writers chaining a single stray write into arbitrary code execution.
Before you call the implementation done, walk failure modes on purpose. Test empty structures, single-element edge cases, maximum concurrency, and errno paths that must not crash the program. OS code usually fails in production when happy-path tests pass but invariants break under contention or memory pressure.
Keep structures small and name fields after kernel counterparts when possible. That lets you read man pages and kernel source side by side while you work. Print observable events during development; remove noisy logs once tests pass reliably.
You will build slab creation, alloc, and free with cache reuse. The task requires demonstrating that repeated alloc/free hits the hot cache without touching the buddy layer after warmup.
Implement a slab allocator, the Linux kernel's allocator for fixed-size objects such as inodes, dentries and task structs. Each cache owns slabs, one 4 KiB page each, cut into equal object slots, and each slab keeps a free list of its slots. A slab is full, partial or empty. Allocation prefers partial slabs, which keeps memory dense. A new page is taken only when every slab is full, and empty slabs can be handed back to the page allocator under memory pressure.
| Command | Effect | Output |
|---|---|---|
cache NAME SIZE | create a cache; the object size is rounded up to a multiple of 8, and a slab holds ⌊4096 / size⌋ objects | cache NAME: object size S, N objects per slab |
alloc NAME | allocate one object | alloc NAME: 0xADDR (slab 0xBASE, U/N used, STATE), preceded by alloc NAME: new slab 0xBASE when a page had to be taken |
free NAME 0xADDR | free an object | free NAME 0xADDR: slab 0xBASE OLDSTATE -> NEWSTATE |
info NAME | show the three lists | NAME: full [..] partial [..] empty [..], U/T objects in use (slab bases ascending; T = slabs × N) |
shrink NAME | release every empty slab | shrink NAME: released K slabs (1 slab) |
0x10000 and increasing by 0x1000. Pages are never reused, even after shrink.k is at base + k × size.CMD NAME: no such cache; cache NAME: already exists; cache NAME: bad object size S (valid sizes are 8-2048); free NAME 0xADDR: not in this cache, ...: not an object boundary, or ...: double free.Addresses print in lower-case hex with 0x.
Input:
cLoading…
Output:
cLoading…
free must find the slab from the address alone (base ≤ addr < base + 4096) and validate it before touching the free list.Hidden tests cover two caches sharing the page allocator, a free that turns a full slab partial and then empty, shrink followed by new allocations (which take fresh pages), and every error.