Virtual Allocator — vmalloc
Status: Not yet implemented. This chapter documents the design and what needs to be built.
How vmalloc Differs from kmalloc
kmalloc | vmalloc | |
|---|---|---|
| Returns | Physical address (identity mapped) | Virtual address |
| Backed by | Heap region in identity-mapped space | Any physical frames, mapped on demand |
| Contiguous? | Physically contiguous | Virtually contiguous, physically scattered |
| Use case | Small kernel allocations | Large allocations, kernel buffers |
| Location | Low memory (~0x00200000+) | High virtual memory (~0xC0000000+) |
How vmalloc Works
vmalloc(size)
│
├─ Round up size to page boundary
├─ Find N free virtual pages in the vmalloc region
├─ For each of the N pages:
│ ├─ allocate_frame() ← get a physical frame
│ └─ map_page(frame, virt, flags) ← map it into virtual space
└─ Return first virtual address
The physical frames don’t need to be contiguous — each is independently mapped to a contiguous virtual range.
The vmalloc Region
0xFFFFFFFF ───────────── 4GB
(end of vmalloc region)
vmalloc allocates here:
virtual pages mapped to
scattered physical frames
0xC0000000 ───────────── 3GB
(start of vmalloc region)
0x00400000 ───────────── ← End of identity mapping
(kmalloc lives here)
Tracking Virtual Allocations
Unlike kmalloc, where the header is adjacent to the data, vmalloc needs a separate metadata structure. We need to know, for each vmalloc allocation:
- Which virtual address range is in use
- How many pages it spans (so
vfreeknows how many to unmap)
One approach: a small array of VmallocRegion structs in kernel memory:
#![allow(unused)]
fn main() {
struct VmallocRegion {
virt_start: u32, // Virtual start address
page_count: u32, // Number of pages allocated
in_use: bool, // Is this slot taken?
}
static mut VMALLOC_TABLE: [VmallocRegion; MAX_VMALLOC_REGIONS] = ...;
}
Functions to Implement
#![allow(unused)]
fn main() {
// memory/virtual_mem.rs
pub unsafe fn vmalloc(size: usize) -> *mut u8;
// Allocate `size` bytes of virtual address space
// Backed by physical frames via map_page()
// Returns null on failure
pub unsafe fn vfree(ptr: *mut u8);
// Unmap and free all pages for this allocation
// Calls unmap_page() + free_frame() for each page
pub unsafe fn vsize(ptr: *mut u8) -> usize;
// Return the size of the vmalloc allocation at ptr
}
The vmalloc Address Allocator
We need a way to find free virtual address ranges in the vmalloc region. The simplest approach: start at 0xC0000000 and walk forward, skipping ranges that are in use. Maintain a VMALLOC_NEXT pointer similar to the frame allocator’s bump pointer.
Key Differences in vfree
vfree must do more work than kfree:
#![allow(unused)]
fn main() {
pub unsafe fn vfree(ptr: *mut u8) {
// Find the allocation record for ptr
let region = find_vmalloc_region(ptr as u32);
// For each page in the allocation:
for i in 0..region.page_count {
let virt = region.virt_start + i * PAGE_SIZE;
// Get the physical frame address before unmapping
let phys = get_physaddr(virt as *const u8) as u32;
// Unmap the virtual page
unmap_page(virt);
// Return the physical frame to the frame allocator
free_frame(phys & !0xFFF);
}
// Mark the allocation record as free
region.in_use = false;
}
}
When to Use vmalloc vs kmalloc
Use kmalloc for:
- Small structs and buffers (< a few KB)
- Allocations that need physically contiguous memory
- Performance-critical allocations (vmalloc has overhead from page mapping)
Use vmalloc for:
- Large buffers (several MB)
- Allocations where physical contiguity doesn’t matter
- When you need more memory than fits in the kmalloc heap