How the CPU Translates Virtual Addresses to Physical Addresses
How does the CPU knows which physical address is mapped to which virtual address?
Introduction
In modern operating systems, processes operate in an isolated virtual memory space, unaware of the underlying physical memory. This abstraction, managed by the CPU's Memory Management Unit (MMU), allows for efficient and secure memory allocation. But how does the CPU know which physical address corresponds to a given virtual address? This article explores the mechanics behind virtual-to-physical address translation, page tables, context switching, and more, complete with code examples and diagrams.
Table of Contents
Virtual Memory Overview
Role of the MMU and Page Tables
Multi-Level Page Tables
Context Switching and the CR3 Register
Translation Lookaside Buffer (TLB)
Code Examples
Virtual Addresses in Parent and Child Processes
Translating Virtual to Physical Addresses
Inspecting Process Memory Maps
Conclusion
1. Virtual Memory Overview
Virtual memory allows each process to believe it has exclusive access to the system's memory. The MMU translates virtual addresses (used by software) into physical addresses (used by hardware). Key benefits include:
Isolation: Processes cannot access each other's memory.
Efficiency: Physical memory is dynamically allocated as needed.
Security: Memory permissions (read/write/execute) are enforced.
2. Role of the MMU and Page Tables
The MMU uses page tables—hierarchical data structures maintained by the OS—to map virtual addresses to physical addresses. Each process has its page table, updated during context switches.
Page Table Basics
Page: A fixed-size block of memory (typically 4 KB).
Page Table Entry (PTE): Contains the physical address of a page and flags (e.g., present, writable).
3. Multi-Level Page Tables
Modern CPUs use multi-level page tables to reduce memory overhead. For example, x86-64 uses a 4-level structure:
Translation Process:
The CR3 register holds the physical address of the PML4 table.
Bits 39–47 of the virtual address index into the PML4 table.
Each entry points to a PDP table, indexed by bits 30–38.
This repeats until the PT entry provides the physical page address.
The remaining 12 bits are the offset within the page.
4. Context Switching and the CR3 Register
When the OS switches processes, it updates the CR3 register to point to the new process’s PML4 table. This ensures the MMU uses the correct page table for translations.
Process Control Block (PCB)
The OS stores process-specific data (including CR3) in the PCB. During a context switch:
Save the current process’s state (registers, CR3).
Load the new process’s CR3 into the MMU.
Invalidate the TLB (unless using Process Context Identifiers).
5. Translation Lookaside Buffer (TLB)
The TLB caches recent translations to avoid costly page table walks. On a context switch, the OS invalidates TLB entries unless they’re tagged with a Process Context Identifier (PCID).
6. Code Examples
Example 1: Virtual Addresses in Parent and Child Processes
Code (virtual_address.c
):
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int var = 42;
printf("Parent PID: %d\n", getpid());
printf("Parent var address: %p\n", &var);
pid_t pid = fork();
if (pid == 0) {
printf("Child PID: %d\n", getpid());
printf("Child var address: %p\n", &var);
var = 84;
printf("Child var value: %d\n", var);
_exit(0);
} else {
wait(NULL);
printf("Parent var value: %d\n", var);
}
return 0;
}
Compile and Run:
gcc virtual_address.c -o virtual_address
./virtual_address
Expected Output:
Parent PID: 9079
Parent var address: 0x7ffe197e5700
Child PID: 9080
Child var address: 0x7ffe197e5700
Child var value: 84
Parent var value: 42
Explanation:
Parent and child share the same virtual address for
var
.Due to copy-on-write, they point to different physical addresses.
Example 2: Translating Virtual to Physical Addresses
Using /proc/[pid]/pagemap
:
The Linux kernel exposes physical page mappings via /proc/[pid]/pagemap
.
Code (pagemap.c
):
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_MASK (((1ULL << 55) - 1) & ~((1ULL << PAGE_SHIFT) - 1))
int main() {
int var;
int fd;
uint64_t entry, pfn;
off_t offset;
printf("Virtual address of var: %p\n", &var);
fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
offset = (uintptr_t)&var / PAGE_SIZE * sizeof(entry);
if (lseek(fd, offset, SEEK_SET) == (off_t)-1) {
perror("lseek");
close(fd);
return 1;
}
if (read(fd, &entry, sizeof(entry)) != sizeof(entry)) {
perror("read");
close(fd);
return 1;
}
close(fd);
if ((entry & (1ULL << 63)) == 0) {
printf("Page not present\n");
return 1;
}
pfn = entry & PFN_MASK;
printf("Physical address: 0x%lx\n", (pfn << PAGE_SHIFT) | ((uintptr_t)&var & (PAGE_SIZE - 1)));
return 0;
}
Compile and Run (as root):
gcc pagemap.c -o pagemap
sudo ./pagemap
Expected Output:
Virtual address of var: 0x7ffe25e5e3e8
Physical address: 0x1220003e8
Explanation:
The program reads its own pagemap to resolve the physical address of
var
.Requires root privileges due to security restrictions.
Example 3: Inspecting Process Memory Maps
Use pmap
to view a process’s memory regions:
pmap [pid]
Output:
1234: ./virtual_address
00007f8d4a2e9000 4K r---- libc.so.6
00007f8d4a2ea000 2048K r-x-- libc.so.6
...
Explanation:
Shows virtual memory regions mapped to physical pages or files.
7. Conclusion
The CPU translates virtual addresses to physical addresses using page tables managed by the OS. Each process has its table, and the MMU’s CR3 register is updated during context switches.
The TLB caches translations for speed, while the OS ensures isolation and correctness through careful management of page tables and process states. By understanding these mechanisms, developers can write more efficient and secure systems software.
Suggestion -you can add links to additional resources