CSpace
A thread’s CSpace (capability space) is the namespace through which it addresses capabilities. A CSpace is a tree of CNodes rooted at the thread’s CSpace root capability (slot 2 in the initial CSpace layout). The kernel resolves capability addresses by walking this tree using a guard-and-radix scheme inherited from seL4.
Global Slot Array
All live capabilities are stored in a single, boot-time allocated array of CapSlotStorage entries.
CNodes do not embed capability payloads; they store CapRef indices into this global array.
Sizing
cap::init() allocates the array during boot based on available physical memory:
num_slots = clamp(free_frames / 4, 768, 131_072)
The floor of 768 covers the standard 6-service boot with test forks. Above approximately 3,000 frames the linear term dominates.
The array is allocated as contiguous physical frames mapped through the direct physical map. A companion bitmap tracks which slots are free.
Slot Storage
Each entry in the array stores both the capability payload and its metadata:
#[repr(C)]
pub struct CapSlotStorage {
pub cap: Capability, // 32 bytes
pub meta: CapSlotMeta, // CDT + untyped tracking + state
}
#[repr(C)]
pub struct CapSlotMeta {
pub cdt_parent: CapSlot, // CDT parent slot
pub cdt_first_child: CapSlot, // CDT first child
pub cdt_next: CapSlot, // CDT next sibling
pub cdt_prev: CapSlot, // CDT previous sibling
pub ut_first_child: CapSlot, // Untyped child list head
pub ut_next: CapSlot, // Untyped child list next
pub ut_parent: CapSlot, // Parent untyped slot
pub state: SlotState, // Free | Occupied
}
CapSlot is u32. INVALID_SLOT = 0xFFFF_FFFF serves as the null marker.
The metadata carries two independent linked-list structures:
- CDT links (
cdt_parent,cdt_first_child,cdt_next,cdt_prev) -
Track the Capability Derivation Tree.
- Untyped links (
ut_first_child,ut_next,ut_parent) -
Track which objects were retyped from which untyped memory.
These structures are related but distinct. A retyped object has both a CDT parent (the source untyped capability that was invoked) and an untyped parent (the untyped memory region that physically backs it).
CNode Structure
A CNode is a kernel object that stores an array of CapRef entries.
Each CapRef is a 4-byte index into the global slot array.
Memory Layout
#[repr(C)]
pub struct CNode {
pub header: KernelObject, // 12 bytes (size_bits = log2 of slot count)
pub guard_bits: u8, // 1 byte
_pad: [u8; 3], // 3 bytes
pub guard: u64, // 8 bytes
// CapRef[2^size_bits] follows contiguously in memory
}
The CapRef array begins immediately after the guard field and is accessed via pointer arithmetic.
Size Constraints
|
Default to 10 (1,024 slots) |
|---|---|
|
Rejected ( |
|
Accepted as-is (16 to 65,536 slots) |
|
Rejected ( |
CNode::init_at() writes the header, sets guard_bits = 0 and guard = 0 (flat mode), and fills every slot with CapRef::null() (INVALID_SLOT).
CapRef
#[repr(C)]
pub struct CapRef {
pub slot: u32, // index into global slot array
}
A null CapRef has slot = INVALID_SLOT = 0xFFFF_FFFF.
Because CNodes store references rather than embedded payloads:
-
Multiple CNodes can point to the same global slot via the same
CapRef. -
Move operations transfer CSpace visibility without copying capability state.
-
CDT and untyped tracking remain attached to the actual global slot.
Deleting a CNode entry (setting it to CapRef::null()) does not delete the capability.
It only removes one CSpace-level reference to a global slot.
Address Resolution
Capability addresses are resolved by walking the CNode tree from a thread’s CSpace root. Each CNode level consumes a portion of the address bits.
Algorithm
Given a capability address cap_addr and a total address width addr_bits:
-
Check recursion depth against
MAX_RESOLVE_DEPTH = 8. -
Read the current CNode’s
guard_bitsandradix(fromheader.size_bits). -
Verify that
bits_remaining >= guard_bits + radix. -
If
guard_bits > 0, extract and compare the guard prefix from the most significant remaining bits. Fail withGuardMismatchif they differ. -
Extract the radix-width slice to compute a slot index.
-
If no address bits remain, the resolution is complete — return the capability at this slot.
-
If bits remain and the slot contains a CNode, recurse into that CNode with the remaining bits.
-
If bits remain but the slot does not contain a CNode, fail with
InvalidSlot.
Address Bit Interpretation
A capability address is consumed left-to-right as a sequence of guard-then-radix segments:
[guard₁][radix₁][guard₂][radix₂]...[guardₙ][radixₙ]
For example, a two-level CSpace with a 10-bit root CNode (guard_bits=0, size_bits=10) containing a 6-bit second-level CNode (guard_bits=0, size_bits=6) at slot 5:
Address: 0x0005_002A (16 bits)
[ 10 bits ][ 6 bits ]
[root index ][ L2 idx ]
[ 5 ][ 42 ]
Resolution follows slot 5 in the root CNode to find the L2 CNode, then indexes slot 42 in L2.
Error Types
Address resolution distinguishes specific failure modes:
|
Slot index out of bounds, or remaining bits but no CNode to follow. |
|
The addressed slot contains |
|
The guard prefix in the address does not match the CNode’s guard value. |
|
Resolution exceeded |
Initial CSpace
The init task receives a statically-backed CNode with 4,096 slots (size_bits = 12).
The kernel populates well-known slots during bootstrap:
| Slot | Name | Description |
|---|---|---|
0 |
|
Thread’s own TCB |
1 |
|
Thread’s page table root |
2 |
|
Thread’s CNode root |
6 |
|
PS/2 keyboard I/O port (x86_64) |
7 |
|
PS/2 keyboard IRQ handler (x86_64) |
8 |
|
COM1 serial I/O port |
9 |
|
COM1 IRQ handler |
10 |
|
COM1 IRQ notification |
11 |
|
IRQ control capability |
12 |
|
Initrd device untyped |
13 |
|
Framebuffer device untyped |
15 |
|
PCI config space I/O port |
16+ |
|
Untyped memory capabilities (one per available region) |
The aarch64 init CNode reuses the same three mandatory slots (0/1/2), then fills the I/O and untyped-range slots with MMIO-oriented objects (PL011 device untyped, interrupt handler).
Every other slot is an init-only convenience carved by init.rs; userland library code MUST NOT hardcode these positions.
Role-Based Capability Table (Children)
Child processes spawned by procmgr no longer receive a fixed slot map.
Only the three mandatory slots are stable for every child:
| Slot | Name | Description |
|---|---|---|
0 |
|
Thread’s own TCB |
1 |
|
Thread’s page table root |
2 |
|
Thread’s CNode root |
16+ |
|
Untyped memory capabilities (procmgr-owned range) |
All service endpoints (procmgr, vfs, namesrv, mmsrv, rsrcsrv, console, win32srv, …) arrive via a role capability table that the spawner builds and attaches to the new process. Userland resolves the capability by role, never by slot number.
Delivery: Auxv Tag
The spawner puts a pointer to a TronaCapTableV1 header on the child’s stack and exposes it through a single auxv entry:
| Constant | Value |
|---|---|
|
|
|
|
|
|
Each table row binds a role to a cursor-allocated CSpace slot plus the rights / flags that spawner policy granted.
Role ID Space
| Range | Meaning |
|---|---|
|
System roles — well-known endpoints/notifications/untypeds defined in |
|
Local roles — process-local identifiers reserved by the spawner; not portable across deployments. |
|
Reserved for future raw / authority roles. |
Lookup happens at runtime through trona::caps::* helpers — callers ask for a role and get back a resolved Capability, or None when the role was not provisioned. There are no hardcoded slot numbers anywhere in the child image.
Rights Bits (CAP_TBL_RIGHT_*)
| Bit | Name | Meaning |
|---|---|---|
|
|
Row is readable (baseline for every live entry). |
|
|
Allows operations that mutate receiver state. |
|
|
Allows forwarding the capability via |
|
|
Allows |
|
|
Row carries a badge; receiver must treat source as an authenticated client. |
|
|
Capability points at device-class memory (MMIO / I/O port / device untyped). |
Flags Bits (CAP_TBL_FLAG_*)
| Bit | Name | Meaning |
|---|---|---|
|
|
Row was minted with a non-zero badge. |
|
|
Authority-grade capability (e.g. |
|
|
Absence is legal; callers must tolerate |
|
|
Underlying object is a Notification, not an Endpoint. |
|
|
Untyped memory. |
|
|
Device untyped (non-zeroed on retype). |
|
|
IoPort capability (x86_64 port range, aarch64 MMIO window). |
The BADGED / RAW / NOTIFICATION combinations let trona::caps pick the right runtime wrapper — a NOTIFICATION row returns a Notification, a plain endpoint row returns an Endpoint, and RAW gates access to authority-only operations.
Error Conditions and Edge Cases
Global Slot Exhaustion
When alloc_slot() returns None, no new capabilities can be created.
This affects copy, mint, save_caller, and retype — all operations that allocate global slots.
Slot count is fixed at boot (clamp(free_frames / 4, 768, 131_072)).
A system with heavy capability derivation (deep fork chains, many services) can exhaust slots before physical memory.
There is no runtime slot expansion.
Multi-Level Resolution Failure Modes
Address resolution can fail at any level in the CNode tree:
| Error | Scenario |
|---|---|
|
The address bits intended as the guard prefix do not match the CNode’s |
|
The CNode tree is deeper than |
|
The address has remaining bits but the current slot does not contain a CNode — resolution cannot continue. Also returned when the slot index exceeds the CNode’s capacity. |
|
The addressed slot contains |
CNode Destruction Cascade
Destroying a CNode object triggers recursive capability deletion:
-
destroy_object(CNode)iterates everyCapRefin the CNode. -
For each non-null
CapRef,CDT::delete_capability()runs the full deletion lifecycle. -
If a deleted capability is itself a CNode, its destruction triggers another recursive walk.
Recursion depth is bounded by MAX_RESOLVE_DEPTH (8) and MAX_DERIVATION_DEPTH (64).
In practice, CNode trees rarely exceed 2-3 levels.
| Deleting a CNode that contains the only references to critical objects (endpoints, VSpaces) will destroy those objects. This is the intended authority model: CNode ownership implies authority over everything it names. |
Related Pages
-
Capabilities — capability structure and operations
-
Object Model — kernel object types and lifecycle
-
Design Patterns — capability delegation and confinement patterns
-
Capability Invocations — CNode invoke labels
-
Boot Sequence — init task CSpace setup