Haiku POSIX Semaphores: User-Space Counts and Named Kernel Objects
Trace Haiku's POSIX semaphore wrappers from sem_t to kernel calls, compare named and unnamed paths, and verify release-specific behavior.
Haiku exposes both its native kernel semaphore API (create_sem(), acquire_sem(), and release_sem()) and the POSIX <semaphore.h> interface. They solve related synchronization problems but are different contracts. A sem_id is a Haiku kernel-object identifier. POSIX code should use the sem_t type through the semaphore functions and should not depend on its representation for portability. Haiku’s public header currently defines sem_t fields; their visibility does not make those implementation-specific fields a portable application ABI.
That distinction is especially useful when porting code. A program can keep the POSIX calls and still run on Haiku, but it should not assume that Linux’s internal sem_t layout, linker flags, filesystem representation, or timing behavior applies. The upstream Haiku source tree reviewed here implements named and unnamed POSIX paths differently. These are source-level observations, not promises that every historical or future Haiku release has an identical implementation. Compile and test against the exact Haiku image you deploy.
Use the public interface, not the exposed representation
Haiku’s public <semaphore.h> declares sem_t and the familiar functions: sem_init, sem_destroy, sem_open, sem_close, sem_unlink, sem_wait, sem_trywait, sem_timedwait, sem_post, and sem_getvalue. The current header exposes fields, but portable application code should not inspect or rely on them, serialize sem_t bytes, compare internal IDs, or copy an initialized object as if it were a portable integer handle. Use semaphore objects only through the operations defined by the target’s POSIX interface.
In Haiku’s current upstream src/system/libroot/posix/semaphore.cpp, a named sem_open() allocates a wrapper and asks the kernel to open the named object. sem_close() and sem_unlink() then call their corresponding kernel operations. The implementation can return an already-used wrapper when the process has opened the same name before. At the application level, use the returned pointer only through the POSIX functions and handle SEM_FAILED plus errno on failure.
The unnamed path is different. The current source’s sem_init() marks the sem_t as private or process-shared and initializes a count in the object. sem_trywait() and sem_post() use atomic count updates; when a wait must block, the implementation reaches a kernel wait mechanism. For an unnamed semaphore initialized with nonzero pshared, the current implementation marks kernel waits as shared. That detail explains the internal route, but applications should follow the POSIX contract: a process-shared unnamed semaphore must reside in memory actually shared by the participating processes. A stack-local sem_t with pshared != 0 does not create shared memory.
Select the API from the lifetime and scope
For threads within one process, an unnamed semaphore with pshared=0 avoids a global name and can live inside an object whose lifetime the application controls. Initialize it once before workers use it; destroy it only after no thread can be waiting on or posting it. A small same-process smoke test can check the target’s public API:
#include <semaphore.h>
int
main(void)
{
sem_t gate;
if (sem_init(&gate, 0, 0) == -1)
return 1;
if (sem_post(&gate) == -1) {
sem_destroy(&gate);
return 1;
}
if (sem_wait(&gate) == -1) {
sem_destroy(&gate);
return 1;
}
return sem_destroy(&gate) == -1 ? 1 : 0;
}
This test checks an uncontended post/wait sequence; it does not prove that interprocess sharing, timeout behavior, process death, or a release’s full POSIX surface works. Add a two-thread test for blocked waiters and a separate shared-memory test only when the application needs those cases. Retry or report EINTR deliberately in a real wait loop; do not turn every failure into a success.
Use sem_open() only when independently started processes need to rendezvous through a named object. Pair every successful open with sem_close(). Call sem_unlink() according to the service’s coordination protocol: unlink removes the name for future lookups, but existing holders may still have an open reference. A crash before cleanup can leave a name that a later run encounters; O_CREAT | O_EXCL is useful for a single designated creator, while silently reusing an old object can inherit an unexpected count. Names are coordination identifiers, not access-control identities; set restrictive permissions and check the effective user and group model on the target.
Do not use a semaphore as a durable queue, transaction log, or crash-recovery record. A count alone cannot tell a restarted process whether the prior owner completed work. Pair it with a separate protocol that can detect stale state and recover safely.
Compare with Haiku’s native kernel API carefully
The native API in OS.h uses sem_id and functions such as create_sem(), acquire_sem_etc(), release_sem_etc(), and delete_sem(). It integrates with Haiku kernel objects and can be appropriate for native code that already uses those APIs. POSIX callers should stay on sem_t and sem_*; converting by guessing that a private sem_t field contains a sem_id couples the program to one implementation revision and bypasses the public contract.
Conversely, not every need for interprocess communication calls for either semaphore flavor. Haiku ports transfer messages, shared areas hold shared memory, and semaphores coordinate counts or wakeups. Choose among them based on whether the application needs message payloads, shared bytes, or signaling. A semaphore protects neither a data structure nor a lifecycle automatically; define the lock, ownership, shutdown, and failure protocol around it.
Verify on the deployed Haiku release
Build a small probe using the target’s native compiler, then run it on the exact release or image used in production. Record the Haiku release, architecture, compiler, and result. Test error paths such as opening a missing name without O_CREAT, an existing name with O_CREAT | O_EXCL, interrupted waits, and cleanup after a controlled child-process exit if those cases matter. Do not import Linux-specific -lrt guidance or undocumented layout assumptions without checking Haiku’s own toolchain and headers.
The core engineering rule is simple: POSIX sem_t is the portable application boundary; Haiku’s source explains how that boundary is currently implemented, while sem_id belongs to a separate native API. Keep those layers distinct, make the semaphore’s lifetime explicit, and test the release you actually ship.
Related:
- Haiku Kernel Semaphores: Counting, Timeouts, and Safe Shutdown
- Haiku Kernel Condition Variables: Experimental Wait and Notify Semantics
Sources:
- Haiku public POSIX semaphore header - publicly defined
sem_tfields and available functions. - Haiku POSIX semaphore implementation - current upstream wrapper paths for named and unnamed operations.
- Haiku kernel API header - native
sem_idAPI declarations. - The Open Group POSIX
sem_open()specification - named semaphore open, exclusive creation, and handle rules. - The Open Group POSIX
sem_init()specification - private versus process-shared initialization requirements. - Haiku Book: Semaphores - documented native kernel semaphore API; distinct from POSIX
sem_t.