Key structures: allocation, access and lifetime
This section follows a kernel structure from the allocator that supplies its storage to the last operation that releases it in xnu-12377.121.6. Start with process and task identity, continue through thread identity and credentials, then follow Mach capabilities, VM structures, file descriptors, messages and network endpoints into their backing storage. The aim is to connect a field declaration to actual callers: how a pointer is obtained, what makes it valid, which reads are permitted, how an update reaches memory, and what survives logical teardown.
Handbook home · Architecture · Reading guide
Use the structure layout browser to inspect sizes and nested field offsets in the HTML handbook. Its coverage table identifies available structures and explains unavailable ones for each supplied iOS ARM64e configuration. Preprocessing directives are inspectable and editable; the structure chapters explain storage and lifetime beyond the browser's finite inventory.
Direct layout routes include thread machine state, ARM64 saved registers and IPv6 packet headers. The IPv6 guide connects the packet layouts to socket options, routes, neighbor discovery and multicast state.
Start with the process and task pair
| Read | Questions answered |
|---|---|
| The process and its protected data | Where does struct proc come from? How do PID lookup and the current thread reach it? Which fields live in proc_ro, how does exec update identity, and when can the allocation be reclaimed? |
| The task and its shared read-only data | How does the task occupy the second part of the allocation? How do references, threads and task ports reach it? How are task flags and control-port options protected, and how does final task release interact with BSD? |
| Writing protected object fields | What does a field-update macro actually do? How do source and destination checks, physical aliases, PPL entry and the SPTM implementation differ? What synchronization and lifetime duties remain with the caller? |
The existing process lifecycle, process transitions, runtime objects and task capabilities explain larger operations. These structure chapters give those operations a concrete storage and access model.
Continue through thread identity and Mach capabilities
| Read | Questions answered |
|---|---|
| Thread, BSD thread state and protected associations | How do one writable thread/uthread allocation and a separate thread_ro become a usable thread? Which accessors reach the task, process, credentials and exception actions? What do the creation, termination, reaper and final-reference stages each release? |
| Credentials and credential storage | How do an embedded posix_cred, protected ucred and writable ucred_rw form one identity? How does hash uniquing handle a duplicate creator? Why do temporary references and long-term holdings have different counters? How do process replacement and thread caches interact? |
| MAC label storage and ownership | What does a label slot contain, how is it encoded, and who owns it? How do ordinary owned labels differ from credential labels? How do RO updates, policy callbacks, lazy vnode publication and destruction cooperate? |
| IPC spaces, entries and task-local names | How does a name select an entry and validate its generation and right type? How do table reservation, publication, growth, lockless lookup and termination keep entries usable? Which pointers survive a lock drop or table replacement? |
| IPC ports and kernel-object associations | What lives in a port's embedded object, message queue and state-dependent unions? How do right counts, object references, receiver state, kobject association and conversion policy differ? What happens between receive-right destruction and final port storage release? |
These chapters explain the structures behind the existing credential and policy lifetime, port and right lifecycle, and thread execution paths. Read an operation chapter alongside its structure companion: one explains why control reaches the next function, the other explains what that function may read, retain, update or release.
Follow virtual memory into backing and translation storage
| Read | Questions answered |
|---|---|
| VM maps and entries | Where are a map, its header, interval entries and lookup nodes allocated? How do packed backing fields, map references, range/entry locks and list/tree publication cooperate? What changes when a range is clipped, detached or destroyed? |
| VM objects and backing | How does an object acquire resident pages, pager/control associations, shadow/copy relationships and references? Which paging/activity holds survive ordinary reference changes, and how does termination release backing? |
| VM pages and resident storage | Where do page descriptors come from, and how do they name physical frames? How do insertion, lookup, busy/wire state, object and queue locks, detachment and recycling change their meaning? |
| Pmap and translation storage | Who allocates the pmap, root and lower tables, PTD metadata and reverse mappings? How do ordinary, PPL and SPTM storage/update rules differ, and what postpones table or pmap release? |
| Memory-entry capabilities and VM copies | How does a named-entry port own a bounded memory view? Why does a single-object entry contain a copy wrapper? How do borrowed access, retained backing, copy-entry conversion, buffer copying and consuming/nonconsuming cleanup differ? |
Read these alongside the VM operation route. A map entry can exist before an object has resident pages or the pmap has a leaf translation. A page descriptor is metadata about a frame; allocating metadata alone does not fill that frame. A named memory handle can outlive its source mapping, and an established destination mapping can outlive the handle. Each companion identifies the allocation and ownership steps behind those distinctions.
Follow descriptors into file and signature storage
| Read | Questions answered |
|---|---|
| File descriptor tables | How does the embedded filedesc own movable arrays? Which slot flags distinguish reservation, publication and close? What survives growth, fork, exec or a descriptor-number reuse? |
| Fileprocs and fileglobs | Which fields and references belong to one descriptor or the shared open-file state? How do dup, I/O holds, message transfer, guarded storage, offset locking and final close work? |
| Vnodes and vnode identity | How are vnode storage and filesystem identity created, acquired, published, reclaimed and reused? What do iocount, usecount, kernel-use and hold references each protect? |
| UBC and vnode pagers | How do ubc_info, the pager and the VM object acquire their associations? Which holds protect mappings and paging, and what survives truncation, reclaim or forced unmount? |
| Code-signature blob storage | How do RO wrappers, serialized buffers, interior views, retained entitlements and monitor objects differ? How do pointer reconstruction, protected updates, publication and cleanup preserve those relationships? |
Read these with the descriptor operation chapter, VFS route and signature attachment lifecycle. An fd number, fileproc I/O hold, shared fileglob reference, vnode identity and borrowed signature pointer name different resources. Closing one descriptor need not destroy file backing; keeping vnode storage alive need not preserve its filesystem identity through reclaim.
Follow messages and packets into their storage
| Read | Questions answered |
|---|---|
| IPC kmsgs and message storage | How does a fixed metadata element select inline or external message buffers? Which accessors depend on representation? How do staged resource ownership, queue transfer, cleanup and reusable timer messages affect storage release? |
| Sockets and protocol attachments | How do socket allocation, protocol selection, PCB association, fd publication and accept queues fit together? Which use counts, locks, upcalls and detach stages protect the endpoint? |
| Socket buffers and record queues | How do embedded sockbuf headers own mbuf records? What do byte, control and allocation-capacity counters mean? How do buffer serialization, socket locking, copy/peek, drop and flush interact? |
| Internet PCBs and transport storage | Why does TCP use a combined inpcb/tcpcb allocation? How do tuple hashes, wanted references, socket holds, routes, timer entries and subordinate buffers survive attachment and teardown? |
| Mbufs and external storage | Which configuration selects the descriptor layout and allocator? How do inline bytes, shared clusters, packet tags, writable copies, paired rings and cache reuse differ? |
Read these beside the Mach message route and networking operations. A kernel message can own capabilities and VM copies; a socket owns protocol state and queues; an mbuf names a view of packet bytes. Queue membership, reference ownership and storage capacity answer different questions in each family.
Two writable structures and one protected companion
For an ordinary BSD process created through forkproc(), there are two relevant allocations. The proc_task zone supplies one element containing the process followed by an aligned task. A separate proc_ro zone element holds process and task association/security fields. The whole process or task is not placed in the RO zone. Combined-zone construction, process allocation, RO-zone definition.
Mermaid source
flowchart TD
Z["One proc_task zone element"] -->|"first part"| P["struct proc: writable BSD state"]
Z -->|"aligned second part"| T["struct task: writable Mach state"]
P -->|"p_proc_ro"| R["Separate proc_ro zone element"]
T -->|"bsd_info_ro"| R
R -->|"proc_data group"| PD["Identity, credential pointer, signing and syscall-filter fields"]
R -->|"task_data group"| TD["Task flags, control-port options and optional filter pointers"]
The first two arrows describe layout; the others describe pointer links or field groups, not additional allocations. The RO object also carries pr_proc and pr_task backreferences. proc_ro_data and task_ro_data are groups inside struct proc_ro; there is no separate task_ro allocation on this path. Fork initializes the process side, then task creation fills the task side of the same object. RO layout, process-side initialization, task-side attachment.
This diagram deliberately selects the ordinary process-creation path. A corpse can have a task without an initialized BSD process. Kernel bootstrap first creates the kernel task with no supplied RO object, then BSD initialization separately allocates the kernel process's RO object. Follow those bodies rather than extending the shared-object diagram to every case. Process initialization boundary, kernel task creation, kernel process RO creation. The two structure chapters trace these variants.
Follow one field through all of its operations
| Stage | Concrete example | What must be distinguished |
|---|---|---|
| Allocate | forkproc() allocates the combined element; proc_ro_alloc() constructs a stack template and copies it into the RO element |
The allocator slot, initialized object and externally usable object are separate milestones. Template and copy. |
| Locate | proc_find(pid) searches under SMR, attempts a process reference and handles exec replacement |
A PID lookup establishes a lifetime protocol; address arithmetic between the two structure halves alone does not. Lookup, raw conversion. |
| Read | proc_get_ro(p) or task_get_ro(t) validates the RO zone and corresponding backreference before returning the pointer |
Validation checks the association. It does not acquire a new object reference or a field lock. Process accessor, task accessor. |
| Update | proc_setpidversion() copies a selected field; task_ro_flags_set() performs an atomic OR |
Both use the RO API, but a copy and an atomic read-modify-write provide different concurrency guarantees. Identity update, task flag update. |
| Detach | Final task destruction atomically clears pr_task, then releases its hold on the combined allocation |
Removing an association does not necessarily free either allocation immediately. Task cleanup, protected pointer clearing. |
| Reclaim | The last process/task allocation hold permits proc_free() to release the credential, RO element and combined element |
BSD wait-reference retirement crosses an SMR grace period; task references and allocation-hold bits have distinct roles. Deferred process release, two-sided release. |
The chapters use these stages to explain selected fields rather than reproduce every member of the large declarations. Each worked path states the relevant references, locks and configuration. A pointer to protected data still requires an owner that keeps the data alive.
How the RO-zone material fits
Start with read-only zone registration and geometry for the allocator's reserved IDs, RO submap and population policy. Then follow writing protected object fields for concrete process/task updates. Return to mutation checks and atomic updates and free for the general contracts.
Read-only describes the ordinary mapping and controlled update interface when enforcement is enabled; the fields can still change. The selected backends write through a physical-aperture virtual address for the same physical bytes. PPL and SPTM take different paths, and the SPTM write-preparation/completion hooks in this checkout have empty bodies. The update chapter separates what the source implements from external monitor behavior. Legacy ARM copy, SPTM copy, SPTM hooks.
For the surrounding storage lifecycle, use zone allocation and reuse and SMR publication and deferred callbacks. An SMR-protected lookup does not by itself mean the object was allocated with zalloc_smr; the process path uses an explicit deferred callback before releasing its allocation hold.
Keep storage, authority and lifetime separate
Each structure has several contracts. An allocator or protected mapping answers where bytes live; an accessor or capability conversion answers how code reaches an object; a reference, lock or read-side interval answers how long that access is valid. Follow all three before interpreting a field assignment.
| Structure family | Storage question | Access and lifetime question | Companion |
|---|---|---|---|
proc / task / proc_ro |
Which fields belong to the combined writable allocation and which to the shared RO record? | Does this path hold a process reference, task reference, SMR read-side protection or one of the allocation's final ownership bits? | Process, task |
thread / uthread / thread_ro |
Is this state in the combined writable element, the RO companion, or an independently allocated object reached through a pointer? | Is the thread alive, active, IPC-enabled, stopped or still awaiting final reference release? Those are different predicates. | Thread |
ucred / ucred_rw |
Is this an immutable identity value, a protected long-term holding count, or writable hash/reference bookkeeping? | Is the pointer borrowed, retained for this call, or installed in a long-lived owner? Which counter and retirement path correspond to that use? | Credentials |
MAC label |
Does protection cover the slot value or also the policy object that the value names? | Is the label tied to the address of an owner's pointer, privately under construction, or published as credential identity? Which provider destroys slot data? | Labels |
ipc_space / ipc_entry |
Is this a retained space or a location inside a replaceable typed table? | What validates the name's index, generation and right type? What must be retried after the table or port state changes? | Spaces and entries |
ipc_port / kobject |
Which active/inactive or special-port state selects the meaning of a union? | Is this a name's user-reference count, a live right, a port reference, or a referenced task/thread obtained through conversion? | Ports and kobjects |
vm_map / vm_map_entry |
Is this a map allocation, an embedded header, a live entry or a copy entry? | Does a map reference keep the map alive while range/entry locks stabilize the selected interval? What must be relooked up after a wait? | Maps and entries |
vm_object / vm_page |
Is this logical backing, a resident descriptor or actual physical bytes? | Which object reference, paging/activity hold, busy state, wire count or queue lock protects the operation? | Objects, pages |
pmap / PTD / PV |
Is this pmap-object storage, a translation table, its descriptor or reverse-mapping metadata? | Which backend owns the bytes, and which lock, epoch, table pin or retained pmap delays reuse? | Translation storage |
vm_named_entry / vm_map_copy |
Is this a port-owned descriptor, an entry-list wrapper or a separate byte buffer? | Is the next accessor borrowing, the next map acquiring a reference, or the next transfer consuming the container? | Memory capabilities and copies |
filedesc / fileproc / fileglob |
Is this an embedded header, replaceable table array, descriptor record or shared open-file allocation? | Does the caller own a reserved slot, a fileproc I/O hold, a shared reference or an in-flight message reference? | Tables, open-file storage |
vnode / ubc_info / vnode pager |
Does the pointer name a current filesystem identity, cached-file association or pager allocation? | Which generation, iocount, usecount, hold, named-object or paging protection is needed? | Vnodes, UBC and pagers |
cs_blob and its backing |
Is this an RO wrapper, serialized buffer, interior view or separately retained provider object? | Does access have vnode/VM lifetime protection, and which owner must release each allocation? | Signature storage |
ipc_kmsg and its buffers |
Which layout holds the kernel header, descriptors, user data, trailer and auxiliary bytes? | Has the representation changed, which transferred resources are still owned, and is the timer keep-alive protocol delaying storage release? | Message storage |
socket / sockbuf |
Is this endpoint allocation, an embedded queue header or separately allocated packet storage? | Which socket reference, selected protocol lock or buffer-serialization flag protects access and waits? | Sockets, buffers |
inpcb / tcpcb |
Is this one combined allocation, an embedded timer/algorithm record or subordinate heap storage? | Does lookup hold a wanted reference, a socket use count or a table/PCB lock, and what changes on stop-using? | Transport storage |
mbuf / m_ext / ext_ref |
Does the current view select inline bytes or shared external capacity? | Who owns the descriptor, backing reference, packet tags and destructor callback? Software read-only state does not imply an RO-zone mapping. | Packet storage |
The RO update API supplies a checked way to change protected bytes. It does not replace the owner's synchronization, convert an unretained pointer into a retained one, or make an object reached through a protected pointer immutable. Conversely, the writable IPC objects still have precise ownership and authority checks. Follow the chapter's accessor, mutation and teardown bodies together instead of inferring those contracts from const, a zone name or a structure member's spelling.
Worked reading routes
-
A newly created BSD thread: follow the task supplied to
thread_create_internal(), the writable allocation and RO template, BSD initialization, credential holdings, task-list publication and IPC enablement in the thread chapter. Then follow final destruction backward: which references remain after the thread stops executing, and what prevents early reuse of the combined element? -
A process changes identity: start with a concrete UID/GID operation in credential storage, follow template derivation, duplicate lookup, publication under the process credential lock, and release of the replaced owner. Then compare cached real and assumed credentials in the thread chapter. A published process credential and the identity already cached by another thread are separate observations.
-
A policy uses a label slot: follow allocation, slot access and owner verification in MAC label storage, then compare credential labels with a vnode's lazily installed label. Stop at the policy callback where its body is external; a framework-owned slot does not explain the provider's private representation or decision.
-
A task uses a Mach name: start with a numeric name in spaces and entries, resolve the index/generation and required right, then follow the locked port into kobject conversion. Finish with the returned object's reference release. Repeat the walk after receive-right destruction to see why a valid table lookup alone does not establish a live service.
-
An anonymous range becomes readable: start with the map entry, follow its object and offset, the resident descriptor and physical frame, then the translation tables. Use the existing fault walkthrough to connect these steps. Track metadata allocation separately from page acquisition, initialization and PTE installation.
-
A task exports and releases memory: follow a named entry from object construction through its copy wrapper and port, then map the handle in another task. Write down the additional backing reference, release the final send right, and identify why the established mapping remains valid. Compare consuming copyout with a preserving copyout and account for every entry reference.
-
An open file is duplicated and closed: follow slot reservation and publication in descriptor tables, then account for two fileprocs sharing one fileglob. Keep a read in flight while another thread closes one descriptor; record the slot flags, I/O count, shared count and offset lock separately. Repeat after the number is reused.
-
A signed file is mapped and reclaimed: acquire the vnode identity, follow its UBC/pager association into VM, and locate the signature wrapper and byte views. Explain which references survive descriptor close, what protects a page-validation traversal, and how reclaim detaches state before final storage release.
-
A message becomes a queued owner: choose one kmsg layout, follow user representation into kernel descriptors, and account for rights, voucher and out-of-line memory. Compare ordinary destruction with a reusable timer message: clearing message resources and releasing storage are separate events.
-
A socket receives and releases data: follow socket publication, PCB lookup and retention, then record insertion and buffer accounting. Clone an external mbuf view, free one descriptor, and explain why the bytes and conservative read-only flag survive. Finally compare descriptor close, protocol detach, timer draining and storage disposal.
For each route, keep a short ownership ledger beside the source: object or table pointer, how acquired, protecting lock or read-side interval, newly owned reference, transfer point, and matching cleanup. When a helper returns, record both the value and the lock/reference state promised by its caller contract.
Scope and remaining structures
The current route covers process/task/thread associations, credential and label storage, IPC namespace/port/message structures, VM maps/entries/objects/pages, ARM64 translation storage, named memory entries and VM copies, descriptor/open-file storage, vnodes, UBC/pagers, signature attachments, sockets, socket buffers, Internet PCBs and mbufs, with selected normal, failure and teardown paths. It does not enumerate every scheduling field, policy slot provider, kobject type, pager, memory policy, network protocol or configuration. Layout depends on the selected configuration, so arithmetic uses the source's alignment and offset expressions rather than a fixed structure size or an offset copied from another release.
IOKit client, memory-descriptor and memory-map objects remain candidates for the next structure companions; their driver operation chapters provide the current route. Mount, buffer-cache, knote/workloop and other supporting structures also have selected operation coverage without exhaustive structure companions. The new message/network chapters retain explicit limits around generated transport, driver providers, complete configuration matrices and concurrency interleavings.
The examples are source walkthroughs. They do not report a kernel build, device experiment or vulnerability investigation. External policy and monitor implementations remain explicit boundaries.