Why the MMU Is the Hidden Engine Behind Linux Memory Management
This article explains how the Memory Management Unit (MMU) underpins Linux's virtual memory, process isolation, and protection mechanisms, detailing its architecture, address‑translation workflow, TLB caching, practical C implementations, real‑world use cases, and debugging techniques for kernel developers.
MMU Overview
The Memory Management Unit (MMU) bridges the CPU and physical memory, enabling virtual memory, process isolation, and memory protection in Linux. It translates virtual addresses to physical addresses using page tables and enforces per‑page permission bits (read, write, execute, user/kernel).
Linux Memory Management Basics
Linux separates physical memory (actual RAM) from virtual memory (per‑process address spaces). Virtual addresses are mapped to physical pages via a multi‑level page‑table hierarchy, allowing swapping, address‑space expansion, and isolation.
Paging
Memory is divided into fixed‑size pages (commonly 4 KB). Each virtual page number (VPN) is mapped to a physical page frame number (PFN) in the page tables. Multi‑level tables reduce memory overhead and lookup latency.
Core MMU Functions
Address Translation : The MMU splits a virtual address into VPN and offset, looks up the PFN in the TLB or page tables, and combines them to produce the physical address.
Memory Protection : Page‑table entries contain permission bits. The MMU checks these on every access and raises a page‑fault if the operation is not allowed.
Translation Lookaside Buffer (TLB)
The TLB caches recent VPN‑to‑PFN mappings. A TLB hit yields an immediate physical address; a miss triggers a page‑table walk and updates the TLB, dramatically reducing translation latency.
Address‑Translation Example (C)
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#define PAGE_SIZE 4096
#define TLB_SIZE 8
/* Page‑table entry */
typedef struct {
uint32_t virtual_page;
uint32_t physical_page;
bool valid;
} PageTableEntry;
PageTableEntry tlb[TLB_SIZE];
PageTableEntry page_table[1024];
void mmu_init(void)
{
for (int i = 0; i < TLB_SIZE; i++)
tlb[i].valid = false;
page_table[0x00] = (PageTableEntry){0x00, 0x1000, true};
page_table[0x01] = (PageTableEntry){0x01, 0x2000, true};
page_table[0x02] = (PageTableEntry){0x02, 0x3000, true};
}
bool tlb_lookup(uint32_t vp, uint32_t *pp)
{
for (int i = 0; i < TLB_SIZE; i++)
if (tlb[i].valid && tlb[i].virtual_page == vp) {
*pp = tlb[i].physical_page;
return true;
}
return false;
}
void tlb_add(uint32_t vp, uint32_t pp)
{
static int next = 0;
tlb[next] = (PageTableEntry){vp, pp, true};
next = (next + 1) % TLB_SIZE;
}
void page_fault_handler(uint32_t vp)
{
printf("[Page fault] Virtual page 0x%X not mapped
", vp);
}
uint32_t mmu_translate(uint32_t vaddr)
{
printf("CPU issues virtual address: 0x%08X
", vaddr);
uint32_t vp = vaddr / PAGE_SIZE;
uint32_t offset = vaddr % PAGE_SIZE;
uint32_t pp;
if (tlb_lookup(vp, &pp))
printf("TLB hit: VPN 0x%X -> PPN 0x%X
", vp, pp);
else {
printf("TLB miss, walking page table
");
if (vp >= 1024 || !page_table[vp].valid) {
page_fault_handler(vp);
return 0;
}
pp = page_table[vp].physical_page;
printf("Page table: VPN 0x%X -> PPN 0x%X
", vp, pp);
tlb_add(vp, pp);
}
uint32_t paddr = pp * PAGE_SIZE + offset;
printf("Physical address: 0x%08X
", paddr);
return paddr;
}
int main(void)
{
mmu_init();
mmu_translate(0x00001234);
mmu_translate(0x00001ABC);
mmu_translate(0x00003000); // triggers page fault
return 0;
}Memory‑Protection Example (C)
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#define PERM_READ 1
#define PERM_WRITE 2
#define PERM_EXEC 4
#define PERM_USER 8
typedef enum { MODE_USER, MODE_KERNEL } CPU_MODE;
CPU_MODE current_mode = MODE_USER;
typedef struct {
uint32_t start_addr;
uint32_t end_addr;
uint8_t permissions; /* bitmask of PERM_* */
const char *name;
} MemoryRegion;
MemoryRegion memory_regions[] = {
{0x00000000, 0x00100000, PERM_READ | PERM_EXEC, "Kernel code"},
{0x10000000, 0x10100000, PERM_READ | PERM_EXEC | PERM_USER, "User code"},
{0x20000000, 0x20100000, PERM_READ | PERM_WRITE | PERM_USER, "User data"}
};
#define REGION_COUNT (sizeof(memory_regions)/sizeof(memory_regions[0]))
MemoryRegion *find_region(uint32_t addr)
{
for (int i = 0; i < REGION_COUNT; i++)
if (addr >= memory_regions[i].start_addr && addr <= memory_regions[i].end_addr)
return &memory_regions[i];
return NULL;
}
bool check_memory_access(uint32_t addr, uint8_t req)
{
MemoryRegion *r = find_region(addr);
if (!r) return false;
if (current_mode == MODE_KERNEL) return true; /* kernel can access all */
if (!(r->permissions & PERM_USER)) return false; /* user cannot touch kernel */
return (r->permissions & req) == req;
}
void access_violation_handler(uint32_t addr, const char *reason)
{
printf("[Access violation] 0x%08X: %s
", addr, reason);
}
void cpu_access_memory(uint32_t addr, uint8_t req)
{
printf("CPU accesses 0x%08X
", addr);
if (!check_memory_access(addr, req)) {
MemoryRegion *r = find_region(addr);
if (!r)
access_violation_handler(addr, "Invalid address");
else if (current_mode == MODE_USER && !(r->permissions & PERM_USER))
access_violation_handler(addr, "User cannot access kernel space");
else
access_violation_handler(addr, "Permission denied");
return;
}
printf("Access granted
");
}
int main(void)
{
printf("=== User mode ===
");
cpu_access_memory(0x20001234, PERM_WRITE); /* allowed */
cpu_access_memory(0x10002000, PERM_WRITE); /* denied – code is read‑only */
cpu_access_memory(0x00000000, PERM_READ); /* denied – kernel space */
current_mode = MODE_KERNEL;
printf("
=== Kernel mode ===
");
cpu_access_memory(0x00000000, PERM_READ); /* allowed */
return 0;
}MMU Integration in Linux
During boot the kernel enables paging, loads the initial page‑table base into cr3 (or ttbr0 on ARM), and configures page‑size attributes. Core software components include:
The buddy and slab allocators for physical‑memory management.
Multi‑level page‑table structures stored per‑process.
Helper functions such as do_page_fault (handles page‑faults) and mmu_init (initialises early mappings).
Context‑switch code that updates the page‑table base register so each task sees its own virtual address space.
Typical Application Scenarios
Process isolation : Each process gets a private virtual address space; the MMU prevents one process from reading or writing another’s memory.
Shared memory : The same physical page can be mapped into multiple processes, enabling fast inter‑process communication while still respecting MMU‑enforced permissions.
Device drivers : Drivers map device registers with ioremap_nocache. The MMU’s permission bits block unauthorized user‑mode access to these regions.
Debugging Case Study: SPI Driver Crash
A driver mapped the SPI controller registers with ioremap (cached) and then wrote to the mapped address, causing a segmentation fault. The investigation followed three steps:
Verify that ioremap returned a non‑NULL pointer (mapping succeeded).
Confirm the physical address (0x40010000) belongs to the SPI controller via /proc/iomem.
Realise that cached mappings are unsuitable for hardware registers. Replacing ioremap with ioremap_nocache disables caching, eliminates the MMU‑related protection fault, and the driver works correctly.
This illustrates a typical MMU‑related debugging workflow: check mapping, validate the physical address, and ensure correct cache/permission attributes.
Key Takeaways
The MMU provides address translation, per‑page protection, and performance acceleration via the TLB.
Linux’s memory subsystem tightly couples hardware MMU features with kernel data structures (page tables, allocators, fault handlers).
Common pitfalls include using cached mappings for device registers or mis‑configuring permission bits, both of which trigger page‑faults or segmentation faults.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
