Linux Virtual Memory

Last update: 2023-07-08 11:32:23 +0800 +0800
tags: Kernel | Linux | Memory
Available translations: 日本語 / 中文(简体) / 中文(繁體)

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:

  1. PGD — page global directory
  2. P4D — page fourth-level directory
  3. PUD — page upper directory
  4. PMD — page middle directory
  5. PTE — 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 hugetlbfs or MAP_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, generate SIGBUS.

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:

sh
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)