Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku Memory Areas: Shared Mappings, Protection, and Lifetime

Use Haiku areas for large or shared memory with explicit page sizing, clone ownership, protection, locking, and process-lifetime rules.

Haiku’s area API gives a team a named, page-granular virtual-memory mapping that can be cloned into another team. It is a low-level mechanism for shared memory, large buffers, and mappings that need explicit placement or protection. An area is not a message queue, a file, a persistent identifier, or a synchronization primitive. It maps memory; the applications that share it must define how its bytes are structured, when they may be read, and how concurrent access is coordinated.

This distinction is important because areas make pointer-based sharing possible. When two teams map the same underlying memory, one team’s write becomes visible through the other team’s mapping. That removes a copy step, but it does not make the data race-free or transactional. A production design needs a versioned layout, bounds checks, synchronization, and a clear owner for creation and teardown.

An area combines a mapping with an address-space-local identifier

The Kernel Kit’s create_area() function creates a mapping in the caller’s team and returns an area_id. The identifier can be used by other Kernel Kit functions and can help another team locate the source mapping, but the numeric value should not be treated as a durable global handle. It is meaningful for the lifetime of the current kernel state and must not be serialized for use after reboot.

Every team that clones the area receives its own area_id and its own virtual address. The underlying memory is shared; virtual addresses do not have to match. Code should therefore pass offsets and lengths as a stable shared ABI, or deliberately request a same-address clone only when that is a real requirement. A pointer valid in one team is not automatically valid in another.

The creator can find its own area by ID or inspect it using area_info. A client can use find_area() by name, but names are not guaranteed unique. If multiple server instances or clients can create areas with the same label, name lookup alone is ambiguous. Prefer an explicit discovery protocol that identifies the instance and validates the area before using it.

Creation requires deliberate sizing and protection

An area has a base address, size, lock policy, and access protection. Its base and size are page-oriented. The safest sizing pattern is to validate the requested payload size, check for overflow while rounding up to a page boundary, and store the usable payload length separately from the allocated length. Do not assume that an exact address request can succeed; address-space layout, architecture, and existing mappings can prevent it.

size_t allocation = requestedBytes;
if (allocation > SIZE_MAX - (B_PAGE_SIZE - 1))
    return B_BAD_VALUE;
allocation = (allocation + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);

void* base = NULL;
area_id area = create_area("shared-cache", &base, B_ANY_ADDRESS,
    allocation, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA);
if (area < B_OK)
    return area;

The code is illustrative: an application must include the relevant Haiku headers and check its own size, status, and cleanup paths. B_ANY_ADDRESS lets the kernel choose an available region. The access flags define whether the mapping is readable, writable, or executable; shared data should not be executable. Treat access flags as a least-privilege boundary, not as a replacement for application validation.

Locking policy affects memory pressure. A locked area is resident and avoids paging that region, which can be useful for real-time buffers or carefully bounded shared state. It also reduces memory available to the rest of the system. Locking a large area merely to avoid thinking about page faults can worsen global responsiveness and cause unrelated allocations to fail. Measure latency requirements before selecting a locked policy.

Cloning shares bytes, not application-level state

To share an area, a client clones an existing mapping. The clone call returns a new identifier and writes the client’s local base address through the output pointer. The clone protection can be narrower than the creator’s mapping; for example, a reader can receive B_READ_AREA without write access. The creator’s permissions should not be assumed to define the client’s desired access.

void* clientBase = NULL;
area_id clientArea = clone_area("shared-cache-reader", &clientBase,
    B_ANY_ADDRESS, B_READ_AREA, sourceArea);
if (clientArea < B_OK)
    return clientArea;

// Validate a shared header and its declared payload length before access.

The area system does not copy the data on clone and does not implicitly provide copy-on-write. If both sides can write, they see the same storage. A writer should publish a new record only after its fields are complete, using a protocol such as a lock, a sequence counter, or a producer/consumer index with carefully specified memory ordering. Do not assume that a multi-field update is observed atomically merely because it occurs in one process.

The shared layout should have a magic value, version, total size, and offsets rather than raw pointers to heap objects. Pointers are process-local and can become invalid when the client maps the area at another address. Validate every offset against the advertised size using overflow-safe arithmetic before converting it to a pointer. Never trust a shared header from a process you do not control.

Lifetime follows mappings and references

An area_id is invalidated when its mapping is deleted or when the team owning that mapping terminates. Deleting one mapping does not necessarily free the underlying pages while other clones remain. Storage is reclaimed when the final mapping is gone. This means that server shutdown and client shutdown are separate events: one party can release its mapping while another still has access.

A robust protocol needs an ownership rule. The creator can announce that it is shutting down, reject new clients, wait for acknowledgments, and then delete its mapping. Clients should stop reading, release their clones, and report completion. If a process crashes, other participants need a way to detect the dead team and discard its shared state; do not wait forever on an in-memory flag that only the failed process could update.

Area names are useful for diagnostics but are not access-control secrets. A name should not be treated as a capability or authentication token. A client needs an explicit authorization and discovery boundary appropriate to the service. Even when only trusted applications participate, validate all sizes and version fields to protect against incompatible builds.

Synchronization is a separate design decision

Areas do not provide a mutex. If multiple teams update shared state, use a separate synchronization primitive such as a semaphore or lock with a documented ownership and timeout policy. A lock must not be held across arbitrary IPC or a blocking operation if another process needs that lock to make progress. For streaming data, single-writer ring buffers can reduce contention, but they still need rules for wraparound, capacity, producer/consumer indices, and memory visibility.

Do not use a shared Boolean flag as a substitute for synchronization unless the exact atomic and memory-ordering guarantees are established. A correct protocol must consider compiler reordering, CPU memory ordering, process death, timeouts, and stale data. If those constraints are more complex than the copy cost, a BMessage or port-based design may be safer and easier to debug.

Diagnose and test the mapping as a boundary

When create_area() or clone_area() fails, record the returned status code, requested size, protection, and address mode. Distinguish invalid arguments from address-space conflicts and memory pressure. A request for an exact virtual address is especially likely to be fragile when plugins or libraries map at unpredictable locations.

Test with two independent teams, not only two threads in one process. Verify that the client sees the expected header and payload, that read-only clones cannot write, and that deleting one side leaves the other mapping valid until it releases its reference. Also test creator crash, client crash, version mismatch, malformed offsets, and concurrent readers/writers. A unit test of the data structure alone does not prove the cross-team lifetime protocol.

Haiku’s area model is powerful when a large buffer must be shared without repeated copying or when a kernel-facing mapping needs explicit properties. It is not a substitute for IPC semantics. The stable design is to share bytes through a small, versioned ABI, synchronize them with an appropriate primitive, and treat every mapping as an independently owned view with a finite lifetime.

Related:

Sources:

Comments