Linux virtual memory gives each process a protected virtual address space while
letting the kernel share files, overcommit address space, reclaim physical pages,
and place memory on NUMA nodes. The discussion assumes a memory-management unit
(MMU); Linux has a separate nommu implementation for systems without one.
Translation and TLBs #
Every load, store, and instruction fetch uses a virtual address. Hardware page tables translate the address to a physical page frame plus an offset. Linux models the hierarchy with five levels:
PGD— page global directoryP4D— page fourth-level directoryPUD— page upper directoryPMD— page middle directoryPTE— page-table entry
An architecture with fewer hardware levels folds unused Linux levels into an adjacent level. The CPU caches translations in a translation lookaside buffer (TLB), because walking several page tables per access would be expensive. Changing a mapping requires invalidating stale translations on every CPU that can execute the affected address space.
Base-page size is architecture and kernel-configuration dependent. A mapping at a higher page-table level can cover a larger page, reducing page-table memory and TLB pressure. Linux supplies two separate huge-page mechanisms:
- HugeTLB reserves or manages explicit huge pages, commonly through
hugetlbfsorMAP_HUGETLB; capacity and allocation failure are part of its operational contract. - Transparent Huge Pages (THP) promotes eligible mappings according to kernel policy, without a distinct application mapping ABI. Promotion and compaction can introduce latency, so workloads should measure rather than assume a benefit.
Address Spaces and Mappings #
Each userspace address space is described by an mm_struct. Kernel threads
normally have no independent userspace mm; architecture-specific execution
uses an active address-space context when needed. The kernel represents a
contiguous virtual-memory region as a vm_area_struct (VMA). A VMA records its
half-open address range, permissions, flags, backing object, and fault
operations. Modern kernels index VMAs with a maple tree.
An execve(2) creates mappings for executable and shared-library segments.
Other common VMAs include the stack, heap, anonymous mmap(2) mappings,
file-backed mappings, shared memory, and the vDSO. mmap(2) reserves a virtual
range and its access policy; it need not allocate or read all physical pages at
mapping time.
mprotect(2) changes VMA permissions, and munmap(2) removes mappings.
Permission checks require both a suitable VMA and a suitable page-table entry.
Guard pages, non-executable mappings, and address-space layout randomization are
therefore enforcement mechanisms, not merely bookkeeping.
Faults, Demand Paging, and Copy-on-Write #
A page fault transfers control to the kernel because translation is absent or the attempted access violates the current entry’s permissions. The fault handler finds the VMA and decides whether the access is legal:
- A minor fault is resolved without storage I/O, for example by mapping a resident page-cache page or installing a newly allocated zero-filled page.
- A major fault requires storage I/O before execution can continue.
- An invalid address or disallowed access normally generates
SIGSEGV; some object-specific failures, such as an inaccessible file-backed page, generateSIGBUS.
Anonymous memory can initially map a shared read-only zero page. A write faults
and obtains a private physical page. After fork(2), parent and child commonly
share pages through read-only mappings. The first writer takes a
copy-on-write fault, receives a private page, and leaves the other mapping
unchanged. Copy-on-write makes fork() inexpensive for an exec()-bound child
but can make writes after a large fork expensive.
File-backed mappings and read(2) use the page cache. Clean cache pages are
reclaimable because the kernel can reread the file. Dirty shared mappings must
be written back before their frames can be reused. A private file mapping uses
copy-on-write for writes, so its modified pages are anonymous rather than
changes to the backing file.
Physical Allocation and NUMA #
Linux describes physical frames with page descriptors and partitions memory by NUMA node and zone. Zones encode hardware constraints: for example, DMA-capable memory or memory not permanently mapped by the kernel on applicable 32-bit systems. NUMA nodes own zones, free lists, statistics, and reclaim state because access latency depends on the requesting CPU and memory bank.
The buddy allocator supplies physically contiguous page blocks. Order zero is
one base page; order n is 2^n contiguous pages. SLAB-family allocators,
commonly SLUB, obtain pages from the buddy allocator and divide them into caches
of fixed-size kernel objects.
kmalloc() returns physically contiguous kernel memory for sizes supported by
the allocator. vmalloc() constructs a virtually contiguous kernel range from
noncontiguous physical pages, so it has different allocation cost and device-DMA
properties. Driver and DMA APIs should be selected by the device’s addressing
requirements rather than substituting one for the other.
Reclaim, Writeback, Swap, and OOM #
Physical memory pressure causes reclaim. The kernel can:
- drop clean page-cache pages;
- write back dirty file pages and then drop them;
- swap eligible anonymous pages when swap is available; and
- invoke shrinkers for reclaimable caches such as filesystem metadata.
Each zone has watermarks. Crossing the low watermark wakes kswapd for
background reclaim. An allocation under more severe pressure can enter direct
reclaim and block until enough pages are available. Reclaim policy and
multi-generational LRU availability are kernel-configuration and release
dependent; inspect the target kernel rather than assuming an LRU implementation.
Pinned pages, many kernel allocations, unreclaimable device buffers, and pages
protected by policy reduce reclaimable capacity. Fragmentation can also prevent
a higher-order allocation despite ample free memory. Compaction migrates movable
pages to form a contiguous free block; kcompactd can run it in the background,
and an allocating task can compact synchronously. THP and DMA are common
consumers of such blocks.
When reclaim and compaction cannot satisfy an allocation, the kernel applies an
out-of-memory policy. OOM handling may be global, scoped to a memory cgroup, or
constrained by the failing allocation context. An OOM kill is a recovery path,
not evidence that the selected process is the original source of memory growth.
Investigate cgroup limits, memory pressure, allocation patterns, and pinned
memory before changing oom_score_adj.
Observing Memory Behavior #
Use several interfaces together because each reports a different layer:
cat /proc/meminfo
cat /proc/vmstat
cat /proc/pressure/memory
cat /proc/$$/maps
cat /proc/$$/smaps_rollup
cat /proc/$$/numa_maps
vmstat 1/proc/PID/maps lists VMAs. smaps adds per-VMA RSS, proportional-set-size,
anonymous, file, swap, and page-size accounting; smaps_rollup aggregates that
information. numa_maps shows locality and policy. Access to
/proc/PID/pagemap is restricted because physical-mapping information can
weaken security boundaries.
For fault rates, process accounting exposes ru_minflt and ru_majflt through
getrusage(2), while /proc/PID/stat includes per-process minor and major
fault counters. /proc/vmstat provides system-wide pgfault and pgmajfault
counters. Interpret deltas over an interval with latency and I/O metrics:
major-fault counts identify fault paths that needed I/O, but do not independently
prove the storage device caused an application’s latency.
Pressure stall information (PSI) in /proc/pressure/memory reports the time
tasks were stalled because of memory pressure. Pair PSI with vmstat, cgroup
memory.events, and application latency to distinguish harmless cache turnover
from sustained reclaim or cgroup-limit pressure.
Kernel behavior and tunables evolve. The primary references are the Linux
memory-management index, its
concepts overview, and
the target release’s source under
mm/.
Comments
Read or comment on GitHub (sign-in required to comment)