Semaphore

Synchronizing on a semaphore

EVL implements the classic Dijkstra semaphore construct, with an API close to the POSIX specification for the basic operations.

Semaphore services


int evl_create_sem(struct evl_sem *sem, int clockfd, init initval, int flags, const char *fmt, ...)

This call creates a semaphore, returning a file descriptor representing the new object upon success. This is the generic call form; for creating a semaphore with common pre-defined settings, see [evl_new_sem()}(#evl_new_sem).

  • sem

    An in-memory semaphore descriptor is constructed by evl_create_sem(), which contains ancillary information other calls will need. sem is a pointer to such descriptor of type struct evl_sem.

  • clockfd

    Some semaphore-related calls are timed like evl_timedget_sem() which receives a timeout value. You can specify the EVL clock this timeout refers to by passing its file descriptor as clockfd. Built-in EVL clocks are accepted here.

  • initval

    The initial value of the semaphore count.

  • flags

    A set of creation flags for the new element, defining its visibility:

    • EVL_CLONE_PUBLIC denotes a public element which is represented by a device file in the /dev/evl file hierarchy, which makes it visible to other processes for sharing.

    • EVL_CLONE_PRIVATE denotes an element which is private to the calling process. No device file appears for it in the /dev/evl file hierarchy.

    • EVL_CLONE_NONBLOCK sets the file descriptor of the new semaphore in non-blocking I/O mode (O_NONBLOCK). By default, O_NONBLOCK is cleared for the file descriptor.

  • fmt

    A printf-like format string to generate the semaphore name. See this description of the naming convention.

  • ...

    The optional variable argument list completing the format.

  • evl_create_sem() returns the file descriptor of the newly created semaphore on success. Otherwise, a negated error code is returned:

    • -EEXIST The generated name is conflicting with an existing mutex, event, semaphore or flag group name.

    • -EINVAL Either clockfd does not refer to a valid EVL clock, or the generated semaphore name is badly formed, likely containing invalid character(s), such as a slash. Keep in mind that it should be usable as a basename of a device element’s file path.

    • -ENAMETOOLONG The overall length of the device element’s file path including the generated name exceeds PATH_MAX.

    • -EMFILE The per-process limit on the number of open file descriptors has been reached.

    • -ENFILE The system-wide limit on the total number of open files has been reached.

    • -ENOMEM No memory available.

    • -ENXIO The EVL library is not initialized for the current process. Such initialization happens implicitly when evl_attach_self() is called by any thread of your process, or by explicitly calling evl_init(). You have to bootstrap the library services in a way or another before creating an EVL semaphore.

    #include <evl/sem.h>
    
    static struct evl_sem sem;
    
    int create_new_sem(void)
    {
    	int fd;
    
    	fd = evl_create_sem(sem, EVL_CLOCK_MONOTONIC, 1, EVL_CLONE_PRIVATE, "name_of_semaphore");
    	/* skipping checks */
    	
    	return fd;
    }
    

    int evl_new_sem(struct evl_sem *sem, const char *fmt, ...)

    This call is a shorthand for creating a zero-initialized private semaphore, timed on the built-in EVL monotonic clock. It is identical to calling:

    	evl_create_sem(sem, EVL_CLOCK_MONOTONIC, 0, EVL_CLONE_PRIVATE, fmt, ...);
    

    Note that if the generated name starts with a slash (’/’) character, EVL_CLONE_PRIVATE would be automatically turned into EVL_CLONE_PUBLIC internally.


    EVL_SEM_INITIALIZER((const char *) name, (int) clockfd, (int) initval, (int) flags)

    The static initializer you can use with semaphores. All arguments to this macro refer to their counterpart in the call to evl_create_sem().

    struct evl_sem sem = EVL_SEM_INITIALIZER("name_of_semaphore", EVL_CLOCK_MONOTONIC, 1, EVL_CLONE_PUBLIC);
    

    int evl_flush_sem(struct evl_sem *sem)

    This call broadcasts a semaphore causing all waiters to be flushed atomically. Any thread which is currently blocked on the semaphore by evl_get_sem() or evl_timedget_sem() is unblocked and returns with the -EAGAIN error code without being granted any resource.

  • sem

    The in-memory semaphore descriptor constructed by either evl_create_sem() or evl_open_sem(), or statically built with EVL_SEM_INITIALIZER. In the latter case, an implicit call to evl_create_sem() for sem is issued before the semaphore is posted, which may trigger a transition to the in-band execution mode for the caller.

  • evl_flush_sem() returns zero upon success. Otherwise, a negated error code is returned:

    -EINVAL sem does not represent a valid in-memory semaphore descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    If sem was statically initialized with EVL_SEM_INITIALIZER but not passed to any semaphore-related call yet, then any error status returned by evl_create_sem() may be passed back to the caller in case the implicit initialization call fails.


    int evl_open_sem(struct evl_sem *sem, const char *fmt, ...)

    You can open an existing semaphore, possibly from a different process, by calling evl_open_sem().

  • sem

    An in-memory semaphore descriptor is constructed by evl_open_sem(), which contains ancillary information other calls will need. sem is a pointer to such descriptor of type struct evl_sem. The information is retrieved from the existing semaphore which was opened.

  • fmt

    A printf-like format string to generate the name of the semaphore to open. This name must exist in the EVL device file hierarchy at /dev/evl/monitor. See this description of the naming convention.

  • ...

    The optional variable argument list completing the format.

  • evl_open_sem() returns the file descriptor referring to the opened semaphore on success, Otherwise, a negated error code is returned:

    • -EINVAL The name refers to an existing object, but not to a semaphore.

    • -EMFILE The per-process limit on the number of open file descriptors has been reached.

    • -ENFILE The system-wide limit on the total number of open files has been reached.

    • -ENOMEM No memory available.


    int evl_get_sem(struct evl_sem *sem)

    This service decrements a semaphore by one. If the resulting semaphore value is negative, the caller is blocked until a call to evl_put_sem() eventually releases it. Otherwise, the caller returns immediately. Waiters are queued by order of scheduling priority.

  • sem

    The in-memory semaphore descriptor constructed by either evl_create_sem() or evl_open_sem(), or statically built with EVL_SEM_INITIALIZER. In the latter case, an implicit call to evl_create_sem() for sem is issued before a get operation is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • evl_get_sem() returns zero on success. Otherwise, a negated error code may be returned if:

    -EINVAL sem does not represent a valid in-memory semaphore descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    -EAGAIN evl_flush_sem() was called for the semaphore, unblocking all waiters. In this case, the unblocked thread(s) was NOT granted any resource.

    If sem was statically initialized with EVL_SEM_INITIALIZER, then any error returned by evl_create_sem() may be passed back to the caller in case the implicit initialization call fails.


    int evl_timedget_sem(struct evl_sem *sem, const struct timespec *timeout)

    This call is a variant of evl_get_sem() which allows specifying a timeout on the get operation, so that the caller is automatically unblocked when a time limit is reached.

  • sem

    The in-memory semaphore descriptor constructed by either evl_create_sem() or evl_open_sem(), or statically built with EVL_SEM_INITIALIZER. In the latter case, an implicit call to evl_create_sem() is issued for sem before a get operation is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • timeout

    A time limit to wait for the caller to be unblocked before the call returns on error. The clock mentioned in the call to evl_create_sem() will be used for tracking the elapsed time.

  • The possible return values include any status from evl_get_sem(), plus:

    -ETIMEDOUT The timeout fired, after the amount of time specified by timeout.

    -EAGAIN evl_flush_sem() was called for the semaphore, unblocking all waiters. In this case, the unblocked thread(s) was NOT granted any resource.


    int evl_put_sem(struct evl_sem *sem)

    This call posts a semaphore, releasing a resource. If some thread is currently blocked on the semaphore by evl_get_sem() or evl_timedget_sem(), the one leading the wait queue is unblocked. Otherwise, the semaphore count is incremented by one.

  • sem

    The in-memory semaphore descriptor constructed by either evl_create_sem() or evl_open_sem(), or statically built with EVL_SEM_INITIALIZER. In the latter case, an implicit call to evl_create_sem() for sem is issued before the semaphore is posted, which may trigger a transition to the in-band execution mode for the caller.

  • evl_put_sem() returns zero upon success. Otherwise, a negated error code is returned:

    -EINVAL sem does not represent a valid in-memory semaphore descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    If sem was statically initialized with EVL_SEM_INITIALIZER but not passed to any semaphore-related call yet, then any error status returned by evl_create_sem() may be passed back to the caller in case the implicit initialization call fails.


    int evl_tryget_sem(struct evl_sem *sem, int *r_bits)

    This call attempts to decrement the semaphore provided the result does not yield a negative count. Otherwise, the routine immediately returns with an error status.

  • sem

    The in-memory semaphore descriptor constructed by either evl_create_sem() or evl_open_sem(), or statically built with EVL_SEM_INITIALIZER. In the latter case, an implicit call to evl_create_sem() for sem is issued before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • evl_tryget_sem() returns zero on success. Otherwise, one of the following negated error code may be returned:

    -EAGAIN sem count value is zero or negative at the time of the call.

    -EINVAL sem does not represent a valid in-memory semaphore descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    If sem was statically initialized with EVL_SEM_INITIALIZER, then any error returned by evl_create_sem() may be passed back to the caller in case the implicit initialization call fails.


    int evl_peek_sem(struct evl_sem *sem, int *r_val)

    This call returns the count value of the semaphore. If a negative count is returned in *r_val, its absolute value can be interpreted as the number of waiters blocked on the semaphore’s wait queue at the time of the call. A zero or positive value means that the semaphore is not contended.

  • sem

    The in-memory semaphore descriptor constructed by either evl_create_sem() or evl_open_sem(), or statically built with EVL_SEM_INITIALIZER. In the latter case, the semaphore becomes valid for a call to evl_peek_sem() only after a put or [try]get operation was issued for it.

  • r_val

    The address of an integer where to copy the semaphore value on success.

  • evl_peek_sem() returns zero on success. Otherwise, a negated error code may be returned if:

    -EINVAL sem does not represent a valid in-memory semaphore descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.


    int evl_close_sem(struct evl_sem *sem)

    You can use evl_close_sem() to dispose of an EVL semaphore, releasing the associated file descriptor, at which point sem will not be valid for any subsequent operation from the current process. However, this semaphore is kept alive in the EVL core until all file descriptors opened on it by call(s) to evl_open_sem() have been released, whether from the current process or any other process.

  • sem

    The in-memory descriptor of the semaphore to dismantle.

  • evl_close_sem() returns zero upon success. Otherwise, a negated error code is returned:

    -EINVAL sem does not represent a valid in-memory semaphore descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    Closing a statically initialized semaphore descriptor which has never been used in get or put operations always returns zero.


    Events pollable from a semaphore file descriptor

    The evl_poll() interface can monitor the following events occurring on a semaphore file descriptor:

    • POLLIN and POLLRDNORM are set whenever the semaphore count is strictly positive, which means that a subsequent attempt to deplete it by a call to evl_get_sem(), evl_tryget_sem() or evl_timedget_sem() might be successful without blocking (i.e. unless another thread sneaks in in the meantime and fully depletes the semaphore).

    Last modified: Fri, 19 Jul 2024 17:48:09 +0200