| //===-- Linux Semaphore implementation for POSIX semaphores ---------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H | |
| #define LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H | |
| #include "hdr/errno_macros.h" | |
| #include "hdr/limits_macros.h" | |
| #include "hdr/offsetof_macros.h" | |
| #include "hdr/time_macros.h" | |
| #include "hdr/types/clockid_t.h" | |
| #include "hdr/types/mode_t.h" | |
| #include "hdr/types/sem_t.h" | |
| #include "hdr/types/struct_timespec.h" | |
| #include "src/__support/CPP/atomic.h" | |
| #include "src/__support/common.h" | |
| #include "src/__support/error_or.h" | |
| #include "src/__support/libc_assert.h" | |
| #include "src/__support/macros/attributes.h" | |
| #include "src/__support/macros/null_check.h" | |
| #include "src/__support/threads/futex_utils.h" | |
| #include "src/__support/time/abs_timeout.h" | |
| namespace LIBC_NAMESPACE_DECL { | |
| class Semaphore { | |
| Futex value; | |
| unsigned int canary; | |
| // Whether the semaphore is shared between processes. | |
| LIBC_PREFERED_TYPE(bool) unsigned int is_shared : 1; | |
| // A private constant canary used to detect use of uninitialized or | |
| // destroyed semaphores. Chose "SEM1" in ASCII (0x53='S', 0x45='E', | |
| // 0x4D='M', 0x31='1'). | |
| static constexpr unsigned int SEM_CANARY = 0x53454D31U; | |
| // Helper function for timed blocking wait, wait for a required | |
| // absolute timeout. | |
| LIBC_INLINE int wait_until(internal::AbsTimeout timeout) { | |
| while (trywait() != 0) { | |
| auto wait_or = value.wait(/*expected=*/0, timeout, is_shared); | |
| // returned error is not a benign race EAGAIN | |
| if (!wait_or.has_value() && wait_or.error() != EAGAIN) { | |
| // ETIMEDOUT still needs a final trywait() in case a post() raced with | |
| // the deadline | |
| if (wait_or.error() == ETIMEDOUT && trywait() == 0) | |
| return 0; | |
| // Any other error (e.g. EINVAL/EFAULT) | |
| return wait_or.error(); | |
| } | |
| } | |
| return 0; | |
| } | |
| public: | |
| LIBC_INLINE constexpr Semaphore(unsigned int value, bool shared) | |
| : value(value), canary(SEM_CANARY), is_shared(shared) { | |
| // The public sem_t mirrors the layout of the internal Semaphore class. | |
| // At entrypoints sem_t object operate through a reinterpret_cast, | |
| // so they must be the same layout. | |
| static_assert(sizeof(Semaphore) == sizeof(sem_t) && | |
| alignof(Semaphore) == alignof(sem_t), | |
| "The public sem_t type must be of the same size and " | |
| "alignment as the internal semaphore type."); | |
| static_assert(offsetof(Semaphore, value) == offsetof(sem_t, __value), | |
| "The semaphore count must be at the offset of " | |
| "sem_t::__value."); | |
| static_assert(offsetof(Semaphore, canary) == offsetof(sem_t, __canary), | |
| "The canary must be at the offset of sem_t::__canary."); | |
| } | |
| // Sanity check to detect use of uninitialized or destroyed semaphores. | |
| LIBC_INLINE bool is_valid() const { return canary == SEM_CANARY; } | |
| LIBC_INLINE void destroy() { | |
| // Destroying a semaphore while threads are blocked on it is undefined | |
| // behavior. Similarly, using a destroyed semaphore is undefined. | |
| // Therefore no concurrency safe destruction is required here, | |
| // RELAXED memory ordering is sufficient. | |
| value.store(0, cpp::MemoryOrder::RELAXED); | |
| canary = 0; | |
| } | |
| LIBC_INLINE int getvalue() const { | |
| // get value is informational, not a synchronization op. | |
| // RELAXED ordering is enough. | |
| // TODO: handle the case where the semaphore is locked. | |
| return static_cast<int>( | |
| const_cast<Futex &>(value).load(cpp::MemoryOrder::RELAXED)); | |
| } | |
| // Atomically increments the semaphore value and | |
| // wakes one blocked waiter if any. | |
| LIBC_INLINE int post() { | |
| FutexWordType v = value.load(cpp::MemoryOrder::RELAXED); | |
| do { | |
| if (v >= SEM_VALUE_MAX) | |
| return EOVERFLOW; | |
| // RELEASE on success, since post should publish prior writes | |
| // RELAXED on failure, since no synchronization event occurs | |
| } while (!value.compare_exchange_weak(v, v + 1, cpp::MemoryOrder::RELEASE, | |
| cpp::MemoryOrder::RELAXED)); | |
| // Wake one waiter if any, | |
| // waiter selection is left to linux futex implementation. | |
| value.notify_one(is_shared); | |
| return 0; | |
| } | |
| // Attempts to decrement the semaphore value without blocking. | |
| LIBC_INLINE int trywait() { | |
| FutexWordType v = value.load(cpp::MemoryOrder::RELAXED); | |
| while (v > 0) { | |
| // ACQUIRE on success to synchronize with the matching post(). | |
| if (value.compare_exchange_weak(v, v - 1, cpp::MemoryOrder::ACQUIRE, | |
| cpp::MemoryOrder::RELAXED)) | |
| return 0; | |
| } | |
| return EAGAIN; | |
| } | |
| // Blocking wait, decrements the value when positive, or blocks until a | |
| // post() makes it positive again. | |
| LIBC_INLINE int wait() { | |
| while (trywait() != 0) { | |
| // Blocks when value was zero. | |
| // Futex wait re-checks the value atomically, | |
| // so a racing post() before sleep is not lost. | |
| // Spurious wakeups just send back to trywait(). | |
| auto wait_or = | |
| value.wait(/*expected=*/0, /*timeout=*/cpp::nullopt, is_shared); | |
| // A successful wake or EAGAIN both just send it back to trywait(). | |
| // Any other error is unexpected. | |
| if (!wait_or.has_value() && wait_or.error() != EAGAIN) | |
| return wait_or.error(); | |
| } | |
| return 0; | |
| } | |
| // Blocking wait against an absolute CLOCK_REALTIME deadline. | |
| LIBC_INLINE int timedwait(const timespec *abstime) { | |
| return clockwait(CLOCK_REALTIME, abstime); | |
| } | |
| // Blocking wait against a deadline on the given clock: | |
| // CLOCK_REALTIME or CLOCK_MONOTONIC | |
| LIBC_INLINE int clockwait(clockid_t clock_id, const timespec *abstime) { | |
| if (clock_id != CLOCK_MONOTONIC && clock_id != CLOCK_REALTIME) | |
| return EINVAL; | |
| // If the semaphore can be locked immediately, succeed without consulting | |
| // the clock or dereferencing abstime. | |
| if (trywait() == 0) | |
| return 0; | |
| LIBC_CRASH_ON_NULLPTR(abstime); | |
| bool is_realtime = (clock_id == CLOCK_REALTIME); | |
| auto timeout = | |
| internal::AbsTimeout::from_timespec(*abstime, /*realtime=*/is_realtime); | |
| if (LIBC_LIKELY(timeout.has_value())) | |
| return wait_until(timeout.value()); | |
| internal::AbsTimeout::Error err = timeout.error(); | |
| LIBC_ASSERT(err == internal::AbsTimeout::Error::Invalid || | |
| err == internal::AbsTimeout::Error::BeforeEpoch); | |
| switch (err) { | |
| case internal::AbsTimeout::Error::Invalid: | |
| return EINVAL; | |
| case internal::AbsTimeout::Error::BeforeEpoch: | |
| return ETIMEDOUT; | |
| } | |
| __builtin_trap(); | |
| } | |
| // Named semaphore operations. | |
| // creates or opens a named semaphore backed by a file in /dev/shm/. | |
| // When O_CREAT is specified in oflag, mode and value are used for | |
| // initialization. | |
| static ErrorOr<Semaphore *> open(const char *name, int oflag, mode_t mode, | |
| unsigned int value); | |
| // unmaps a named semaphore. | |
| static int close(Semaphore *sem); | |
| // removes a named semaphore from the filesystem. | |
| static int unlink(const char *name); | |
| }; | |
| } // namespace LIBC_NAMESPACE_DECL | |
| #endif // LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H |