Virtual Memory, Pages, Caches, and Hardware Memory
This chapter connects C's memory model to the hardware and OS reality beneath it: virtual memory, pages, CPU caches, and the specialized memories of embedded systems (SRAM, flash, DMA).
Why This Matters
C programs think in terms of a flat address space, but that space is an illusion maintained by the MMU and OS. Caches determine performance; DMA and embedded memories determine correctness in low-level code. This is the bridge from language to machine.
Prerequisites
c.memory.1— the C memory model.
Core Concept
Virtual vs. physical memory
On hosted systems, your program sees virtual addresses, not physical ones. The OS and the MMU translate virtual pages to physical frames. This gives each process an isolated, contiguous address space and enables paging, swapping, and memory protection.
- A page is a fixed-size block of virtual memory (typically 4 KiB).
- The page table maps virtual pages to physical frames.
- The TLB caches recent translations.
CPU caches
The CPU caches frequently used memory in a hierarchy (L1, L2, L3). Accessing cache-resident data is orders of magnitude faster than main memory. Caches operate on cache lines (typically 64 bytes), so locality matters (c.perf.cache-locality).
Embedded memory
Embedded systems have different memories with different properties:
| Memory | Properties |
|---|---|
| Flash | non-volatile, read-mostly, stores code and constants |
| SRAM | fast, volatile, stores variables and stack |
| EEPROM | non-volatile, byte-erasable, for small persistent data |
| MMIO | memory-mapped hardware registers (c.emb.mmio) |
DMA and cache coherency
DMA (direct memory access) lets peripherals read/write memory without the CPU. DMA and CPU caches can disagree about memory contents, so DMA buffers need special handling: uncached regions, cache flushes/invalidations, or coherent memory. EMBEDDED
Examples
Observing page size (POSIX)
#include <stdio.h>
#include <unistd.h>
int main(void)
{
long pz = sysconf(_SC_PAGESIZE);
printf("%ld\n", pz);
return 0;
}
This is POSIX, not ISO C. Expected output is typically 4096.
Cache-friendly vs. cache-unfriendly access
/* stride-1 access is cache-friendly */
for (int i = 0; i < N; i++)
sum += a[i];
/* strided access by a large factor is cache-unfriendly */
for (int i = 0; i < N; i += 64)
sum += a[i];
The first loop touches every cache line once; the second touches one element per line, wasting bandwidth. Full treatment in c.perf.2.
How It Works
The MMU walks page tables (with TLB acceleration) to translate addresses and enforce permissions. The cache hierarchy sits between the CPU and memory, fetching whole cache lines on a miss. DMA controllers bypass the CPU and transfer directly to/from memory.
Variations
MPU (embedded)
Microcontrollers without an MMU often have an MPU (memory protection unit) that enforces regions/permissions but does not provide virtual memory.
Huge pages
Large pages (2 MiB, 1 GiB) reduce TLB pressure for large working sets. This is an OS-level optimization.
Common Mistakes
- Assuming virtual addresses equal physical addresses.
- Ignoring cache-line effects (false sharing, strided access).
- Using a DMA buffer without handling cache coherency.
- Assuming embedded flash is writable like RAM.
Undefined Behavior
- Accessing memory without proper permissions (e.g., writing to a read-only
page) is a hardware/OS fault, not ISO C UB — but the C-level cause (writing to a string literal) is UB.
Portability
- Page size, cache line size, and cache hierarchy are hardware/OS-specific.
- Embedded memory mapping is entirely platform-specific.
Under the Hood
The MMU provides isolation and permission checking. The TLB caches translations. Caches exploit spatial and temporal locality. DMA requires cache coherency protocols or explicit maintenance.
Practical Usage
- Write cache-friendly code (contiguous access, avoid false sharing).
- Use page-aligned buffers for
mmapand DMA. - In embedded code, understand which memory a variable lives in and whether DMA
requires cache maintenance.
Exercises
1. Print the page size (POSIX sysconf) and the cache line size (via sysconf(_SC_LEVEL1_DCACHE_LINESIZE) where available). 2. Benchmark stride-1 vs. strided access to see cache effects. 3. Explain why DMA buffers may require cache flushes. 4. Describe the difference between flash and SRAM in embedded.
Deep Challenge
Design a cache-coherent DMA buffer scheme for an embedded system: explain when you need uncached memory vs. explicit cache invalidate/flush, and write the pseudocode for a DMA transfer with correct ordering. Discuss alignment and volatile.
Related Concepts
c.cpu.3— caches, TLB, MMU, MPU.c.perf.2— cache and memory locality.c.emb.dma— DMA in embedded.
References
- OS/hardware documentation for your target platform.
- ISO C does not define virtual memory, pages, or caches; these are
platform/hardware concepts.
Verification
- Virtual memory, pages, and caches are platform/hardware concepts, not ISO C.
PLATFORM-SPECIFIC
sysconf(_SC_PAGESIZE)is POSIX.PLATFORM-SPECIFIC- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Virtual vs. physical memory
- [ ] Pages and TLB
- [ ] CPU caches and cache lines
- [ ] Embedded flash/SRAM/EEPROM
- [ ] DMA and cache coherency
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.mem.virtual | 0 | 6 |
| c.mem.page | 0 | 6 |
| c.mem.cache | 0 | 6 |
| c.mem.embedded-sram | 0 | 5 |
| c.mem.dma | 0 | 5 |