Virtual memory and kernel allocation
XNU's VM subsystem manages address spaces, the objects supplying their contents, resident physical pages, and the work needed to make pages available under pressure. Its core is in osfmk/vm; process-level memory policy lives in BSD, hardware translation in architecture-specific pmap implementations, and small kernel allocations in osfmk/kern.
Start with maps, objects, pages, and faults, then follow one mapping from API request to CPU access. The detailed chapters below separate address-space policy, content ownership, and hardware translation while tracing their interfaces.
For worked operations spanning other subsystems, use a mapped file through an ARM64 fault and a user buffer through DMA completion. They carry concrete object offsets, virtual/physical/device addresses and resource ownership through the same VM mechanisms.
Detailed reading route
For allocation, field access and final storage release, use the VM structure route: maps and entries → objects → page descriptors → pmap and table storage → named entries and copies. These companions supply field and ownership maps for the operation chapters below.
For concrete clients of the allocator, the key-structures section follows proc and task from their combined allocation into a separate protected record. Writing protected object fields connects their update helpers to the RO-zone validation and ARM64 alias-write paths described below.
| Question | Guide |
|---|---|
What happens during anonymous/file mmap, a first write, COW, mprotect, and munmap? |
Mapping walkthroughs |
| How do API addresses, lengths and protection masks become checked arguments? | VM argument validation: wrapper types, page arithmetic, zero-size policies, compatibility rewriting and target-map authority |
| How does memory acquire or lose execute permission? | Executable-memory policy: MAP_JIT admission, mobile/macOS branches, protection changes, monitor association, ARM64 faults and SPTM frame classification |
| How are ranges found, reserved, split, protected, copied, and removed? | Virtual address maps: entry representation, B+tree storage, interlock/range/entry locks, submaps and APIs |
| Where do contents live, and what keeps them alive? | Objects and resident pages: delayed-copy preservation/versioning, collapse versus bypass, resident/compressed backing, page states and object teardown |
| How does a fault obtain contents and safely install a translation? | Fault resolution: fast/slow cases, COW, pager/compressor work, object/offset and copy-version revalidation, resilient-media recovery and pmap retries |
| What are the ARM64 tables and pmap structures? | ARM64 pmap: 4 KiB/16 KiB geometry, PTEs, roots, physical apertures, PV/PTD metadata and nesting |
| How do concrete addresses become table indices, PTEs and physical bytes? | Pmap translation walkthroughs: numeric 4 KiB/16 KiB walks, decoded PTEs, root selection and mapping changes through reuse |
| How are translations installed, changed, disconnected, and tables freed? | Pmap mapping lifecycle: legacy/PPL and SPTM paths, locks, epochs, table pins and accounting |
| When can CPUs safely use a new translation or stop using an old one? | Pmap TLB and cache operations: ASIDs, activation, access/dirty tracking, invalidation and completion |
| How does the x86 backend differ? | x86_64 pmap: four-level tables, PV lists, root views, PCIDs, shootdowns and EPT boundaries |
| How does a file/device pager supply or preserve pages? | Pagers and UPLs: object-control interfaces, vnode/UBC, page-in/writeback and completion |
| What survives a UPL gather, partial completion or vector error? | UPL gathering and completion: busy/absent/wired pages, delayed page work, access blocking, commit/abort ranges, vector coordinates and child ownership |
| What happens when an I/O request addresses executable memory? | Executable pages and UPL I/O: direction-dependent admission, temporary copying, byte/page coordinates and backing lifetime after the kernel mapping is removed |
| How do ordinary reads share file pages with mappings, and what invalidates those pages? | File-data caching: resident hits, missing runs, read-ahead, truncation and UBC lifetime |
| How do cached file pages become eligible for executable mappings? | Page-signature validation: offset/hash translation, validation/taint/NX masks, reuse, modification and monitor enforcement |
| How are pages wired or ranges exported to another task? | Wiring and memory entries: wire counts, map copies, named capabilities and transfer ownership |
| What happens when a Mach message carries memory and rights and a later stage fails? | Complex message conversion: owned VM copies, receiver alignment, port-array acquisition, partial publication and failed-send compensation |
| How does a copy replace existing bytes, and how can a remap cross page-size boundaries? | Copy-overwrite and remapping: object substitution, physical-page revalidation, partial progress, source extraction and 4 KiB/16 KiB adjustment |
| What happens when copying overlaps replacement of its destination mapping? | Concurrent-operation walkthroughs: retained pages, current-entry preflight, copy-version checks and completed-prefix accounting |
| How are physical pages recovered while logical mappings survive? | Reclamation and pressure: disconnect, worker ownership, compression, swap and process policy |
| How does memory pressure select a process, freeze its contents, and later resume it? | Memorystatus and freezer: health/actions, referenced candidates, task/map/object freeze, budgets, packed segments and distinct frozen states |
| How do kernel allocators select storage, isolate types, and release backing? | Kernel allocation: index for zone caches, kalloc routing, compiler type metadata, heap/zone assignment, kmem guards and specialized allocators |
| How is type isolation enforced, and where does sharing remain? | Typed allocation: fixed signature groups and zone budgets, variable heap assignment, runtime routing and compatible frees |
| How does MTE detect stale or out-of-allocation accesses, and who maintains its tags? | MTE tagging and page lifetime: process policy, checked mappings and aliases, zone/free/SMR retagging, page copies/compression, fault recovery and physical tag-backing reuse |
| How did memory and tag checking become available before normal allocators existed? | ARM64 bootstrap memory: early tables, physical/virtual cursors, page descriptors, MTE configuration/generator setup, early tagged zones and deferred static release |
| How do protected domains constrain mappings and page ownership? | SPTM, TXM, and Exclaves memory: frame types, signing policy, protected calls, donation and shared mappings |
The examples use page-size symbols where possible. Before following an address into pmap, identify the map's page size, system VM page size, and hardware translation granule; these are separate inputs in supported configurations.
The central distinction
A mapping describes what an address means. A resident page holds its current contents. A hardware translation allows a CPU to access those contents. These can exist independently: an untouched anonymous mapping can have neither a resident page nor a page-table entry; a file's cached pages can outlive a particular process mapping. The public vm_map_create documentation explicitly identifies the VM map as the source of truth for region configuration and the pmap as its machine-dependent cache.
Mermaid source
flowchart TD
BSD[BSD mmap, fork, exec] --> MAP[vm_map and vm_map_entry]
MACH[Mach VM APIs and memory entries] --> MAP
MAP --> OBJ[vm_object plus object offset]
MAP --> PMAP[pmap: CPU translations]
OBJ --> PAGE[vm_page: resident contents]
PAGE --> PMAP
OBJ --> PAGER[memory_object pager]
PAGER --> VNODE[Vnode pager / UBC / filesystem]
PAGER --> COMP[Compressor pager / segments / optional swap]
PAGER --> DEVICE[Device pager / IOKit]
SCAN[Pageout scanner] --> PAGE
SCAN --> COMP
PRESSURE[BSD memorystatus and freezer] --> SCAN
ALLOC[zones / kalloc / kmem] --> PAGE
ALLOC --> MAP
The arrows summarize dependency and data flow, not a universal synchronous call chain. Pager dispatch is directly through operation tables in this checkout; do not assume every memory_object_* call sends an IPC message. See memory_object_data_request.
Follow one address in both directions
Mermaid source
flowchart LR
VA[Task virtual address] --> E[Map entry and allowed protection]
E --> O[Object and offset]
O --> R[Resident vm_page or backing pager]
R --> F[Physical frame selected by fault]
VA --> PT[Pmap root and page-table walk]
PT --> PTE[Leaf translation and permissions]
PTE --> F
F --> PV[Reverse mappings]
PV --> PTE
PTE --> TLB[CPU cached translation]
The upper route decides what contents an access is allowed to obtain. The lower route supplies the hardware translation once contents are ready. Reverse mappings let VM begin with a physical page and find affected translations during reclaim or protection changes. A PTE can temporarily grant less access than the map permits, for example to enforce COW or track writes. Replacing or removing a PTE must also honor invalidation completion before the old mapping's resources can be reused. Faults, mapping lifecycle, TLB completion.
Ownership and interfaces
| Component | Owns or describes | Main source entry points |
|---|---|---|
| VM map | Address ranges, permissions, inheritance, submaps, resource limits | struct _vm_map, struct vm_map_entry, vm_map_enter |
| VM object | Logical page contents indexed by object offset, pager and shadow relationships | struct vm_object, vm_object_shadow |
| Resident-page system | Physical-page descriptors, object lookup, free and replacement queues | struct vm_page, vm_page_grab_options |
| Fault resolution | Permission checks, lookup, zero fill, copying, pager requests, pmap insertion | vm_fault, vm_fault_page |
| Pmap | Machine translations, reference/modified tracking, mapping accounting | ARM pmap, SPTM pmap, x86 pmap |
| Pager/UPL interface | Supplying or returning contents; holding pages stable during I/O | memory_object.c, vm_object_upl_request, vm_upl.c |
| Reclamation | Selecting victims, compression, swap, process memory policy | vm_pageout_scan, memorystatus_thread_internal |
| Kernel allocators | Fixed objects, typed size classes, page-granularity kernel allocations | zalloc_ext, kalloc_ext, kmem_alloc_guard_internal |
The most useful cross-subsystem routes are BSD mmap to a vnode's UBC object, VM faults to filesystem page-in, pageout to filesystem writeback, and IOKit memory descriptors to wired pages/device mappings. Continue into the VFS documentation for filesystem operation dispatch. Process creation and termination determine map lifetime; scheduler waits and wakeups make faulting, pageout, and freezer work executable.
Read the selected pmap backend
arm/pmap.h selects the SPTM or legacy ARM representation. files.arm64 selects matching sources; files.x86_64 selects the x86 files. The same common API name is not evidence that all backends share one algorithm.
| Backend | Hardware state | Principal update/lifetime mechanisms |
|---|---|---|
| Legacy ARM, optionally PPL | Per-pmap geometry, root, hardware/software ASID state, reverse mappings and table descriptors | Pmap reader/writer lock, PV-head protection, PPL allocation/retry boundaries when configured |
| ARM64 SPTM | Geometry/root/ASID and reverse/table metadata coordinated with monitor-managed page tables | Monitor update interface, PV-head locks, per-CPU epochs, leaf-table pins and condemnation before reclaim |
| x86_64 | PML4 roots, lower table pages, physical alias lists, per-CPU PCID state | Pmap reader/writer lock, PTE/PV synchronization, root activation and remote invalidation protocol |
Definitions: legacy pmap, SPTM pmap, x86 pmap. SPTM's implementation cannot be explained by replacing a legacy PPL function call with a different entry instruction; its local concurrency and table-lifetime machinery also differ.
| Pmap operation | Direction and purpose |
|---|---|
pmap_create_options / pmap_destroy |
Create and retire an address-translation context |
pmap_enter_options_addr / pmap_enter_options |
Install a selected physical address/page at a virtual address with policy supplied by VM |
pmap_protect* / pmap_remove* |
Change installed access or remove translations for a virtual range |
pmap_page_protect* / pmap_disconnect* |
Begin from a physical page and affect its aliases |
pmap_get_refmod / pmap_clear_refmod* |
Observe or reset access/dirty tracking used by VM |
pmap_switch* / backend activation |
Select a context and satisfy address-space tag/coherency requirements |
pmap_nest / pmap_unnest |
Share or detach selected lower-level hierarchy for supported nested mappings |
Read these as interface families; argument types, helper names, fast paths, and completion contracts differ. common pmap interface, ARM lifecycle, x86 implementation.
Details specific to this checkout
- Map lookup storage is a B+tree, including summaries of holes for allocation. Older descriptions of XNU's red-black VM map tree do not describe
vm_map_store_node. - The map has an interlock and individual entries have locks. Range-lock contexts prepare and stabilize entries, including submap descent and COW resolution. A model with one map-wide lock is insufficient; see
vm_map_lock_internal.h. - Page size belongs to multiple layers. A map has
hdr.page_shift, while VM objects and physical pages use system-page conventions. Code explicitly handles different sizes; never replaceVM_MAP_PAGE_SIZE(map)with a hard-coded 4096 in your mental model. Seevm_object_shadow. - Compressor, general swap, and freezer swap have separate runtime configuration predicates. The presence of their source does not establish that a particular product uses every facility. See
vm_configpredicates. - VM submaps have a constrained hierarchy in the current range-lock implementation; this is separate from sharing a hardware hierarchy through pmap nesting. See
submap rules. - A named memory entry carries a capability to backing described by a submap or
vm_map_copy. The current copy representation has entry-list and kernel-buffer variants. See memory entries and copies. - An invalid compressed PTE marker preserves mapping accounting; compressed contents and slot locators live in the object's compressor pager. See compression.
Sources inspected and limits
“Deep” means the named data definitions and relevant multi-stage implementation paths were read, not that every line or configuration branch in a large file was audited.
| Area | Inspection | What the documentation supports / limits |
|---|---|---|
| Executable memory | Selected BSD admission, VM protection/association, fault policy and ARM64/SPTM classification | MAP_JIT, mobile/macOS W+X distinctions, RW→RX outcomes, copied/remapped authority and 16 KiB examples; external MAC/monitor/JIT-runtime behavior remains bounded |
| Maps and ranges | Deep: vm_map_xnu.h, map-store/range-lock implementation, selected allocate/map/protect/remove/fork/remap paths and kern_mman.c boundaries |
Entry representation, B+tree/hole lookup, lock preparation/restart, submaps and permission changes; no proof of every balancing or concurrent range operation |
| Objects and pages | Deep: vm_object_xnu.h, vm_page.h, allocation/lookup/reference/activity/termination in vm_object.c and vm_resident.c |
Delayed-copy preservation/versioning, collapse/bypass proofs and resident/compressor/pager ownership, page states and deferred teardown; specialized objects/configurations remain bounded |
| Faults | Deep selected vm_fault.c paths: map/submap lookup, resident/COW/zero-fill/compressor/pager work, try-lock relookup and nonblocking pmap insertion retry |
Common success/failure/concurrency paths; code-signing and MTE conditions are identified without claiming every policy branch |
| Copies, memory entries, wiring | Deep: current copy representations and named-entry creation/mapping; VM map/page/pmap wiring and unwiring stages; overwrite substitution/physical-copy retries and remap granule adjustment | Transfer ownership, partial-progress boundaries and residency protection; all API flag/overlapping-alias combinations and generated MIG code remain outside exhaustive review |
| File pager / UPL | Deep selected bsd_vm.c, ubc_subr.c, memory_object.c, vm_pageout.c and vm_upl.c; UPL gather, commit/abort, partial/vector completion and release paths |
VM/VFS handoff, busy/absent/wire/activity ownership, access blocking and vector retirement; fast-gather/error-prefix cleanup coverage remains qualified, while full transpose/provider behavior is separate |
| Reclamation | Deep scanner final ref/mod/disconnect decisions, queue-to-worker activity/busy ownership and compression cleanup in vm_pageout.c; page allocation in vm_resident.c |
Replacement policy and physical-lifetime boundary; tuning constants are not universal product defaults |
| Compressor / swap | Deep pager put/get, slot/segment representation, nonblocking decompression, temporary physical mappings, and swap reserve/submit/complete paths | Offset-to-slot-to-segment-to-page relationship; codec algorithms, every compaction state and swap encryption remain narrower |
| Process policy / freezer | Deep health/action selection, priority/high-water candidate reference/restart, freeze eligibility/iterator, process/task/map/object path and packed-segment completion | Focused chapter separates task and BSD frozen states, evaluation/execution, MiB/compressed-page counter units, work budgets and asynchronous I/O. All aging/aggressive-kill rules, throttle combinations, external recommendation policy and notification filters remain bounded. |
| Allocators | Deep zone geometry/metadata/bitmap/cache/depot and refill/reclaim; kalloc classes, routing/free/realloc; fixed/variable compiler metadata and startup isolation policy; guarded kmem placement/population/free/resize; RO/SMR/perCPU/permanent modes and selected IOKit/BSD wrappers | Separate chapters trace element, zone, VA, physical and protection lifetimes. Exact generated compiler output, live heap assignments, all sanitizer configurations and external monitor enforcement are outside source verification |
| ARM64 pmap | Deep legacy/PPL and SPTM geometry, roots, PTEs/PV/PTD, enter/protect/remove/disconnect, table allocation/reclaim, ASIDs/nesting, epochs/pins, access tracking and TLB completion | Source-visible mapping/lifetime contracts; external monitor bodies and every platform/mapping type are not reconstructed |
| MTE mitigation and physical backing | Deep selected vm_mteinfo.c, map/object/fault, resident-page, SPTM pmap, allocator, exec/copyio/abort and compressor paths |
Companion connects pointer/storage tags, process/root policy, mapping provenance, allocator retagging, copies/compression and fault attribution to backing activation/deactivation and relocation; external monitor/AMFI/user-allocator bodies, every consumer/configuration and the complete codec remain outside the trace |
| x86_64 pmap | Deep create/expand/enter/remove/protect/disconnect, rooted/hashed PV, root sharing, PCID activation and invalidation; selected nesting/EPT branches | Current four-level mapping path; bootstrap, all cache-type changes, corruption heuristics and complete hypervisor behavior remain outside scope |
| Peripheral VM | Focused I/O mapping and driver memory boundaries; sanitize and shared-region interfaces | Shared-region/pager and deferred-reclamation paths are expanded in the companions below; complete IOMMU enforcement and external allocation/policy providers remain bounded |
Useful original documentation includes pageout scan, memorystatus, freezer, shared region, VM sanitization, and allocator API guidance. Prefer implementation when an older narrative uses a superseded function or structure name.
Backing-store and reclaim companions
- Shared regions and transforming pagers: exec placeholders, shared-cache installation, submap/pmap nesting, page transformation and pager lifetime.
- Compressor segments and swapfiles: filling allocation, locator repair during compaction, swap workers, file creation, trim and reclamation.
- Deferred reclamation: userspace publication/cancellation, kernel gate ownership, partial progress, sampling, fork and teardown.
- Purgeable memory: volatile/empty transitions, aging tokens, selection locks and resident/compressed owner accounting.
- Phantom cache and refaults: bounded eviction history and conditional pressure decisions, including the macOS ARM64 exclusion.