Synchronizing on a group of flags

An event flag group is an efficient and lightweight mechanism for synchronizing multiple threads, based on a 32bit value, in which each individual bit represents the state of a particular event defined by the application. Therefore, each group defines 32 individual events, from bit #0 to bit #31. Threads can wait for some bits among an arbitrary set to be posted to the flag group (aka disjunctive wait mode), or for an exact set instead (aka conjunctive wait mode).

The current value of a flag group is composed of the set of pending bits which have not yet been consumed by waiters. Threads waiting for flags to be posted to a group receive them by order of scheduling priority.

Alt text

The EVL event flag group is most commonly used as a notification mechanism between out-of-band threads. However, in-band threads may post and receive events from a flag group as well. See this section about using in-band I/O to access a flag group.

Unlike with the eventfd, there is no semaphore semantics associated to an event flag group, you may want to consider the EVL semaphore feature instead if this is what you are looking for.

Event flag group services


int evl_create_flags(struct evl_flags *flg, int clockfd, int initval, int flags, const char *fmt, ...)

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

  • flg

    An in-memory flag group descriptor is constructed by evl_create_flags(), which contains ancillary information other calls will need. flg is a pointer to such descriptor of type struct evl_flags.

  • clockfd

    Some flag group-related calls are timed like evl_timewait_some_flags() 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 flag group. You can use this parameter to pre-set some bits in the received event mask at creation time.

  • 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 flag group 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 flag group name. See this description of the naming convention.

  • ...

    The optional variable argument list completing the format.

  • evl_create_flags() returns the file descriptor of the newly created flag group 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 flags is wrong, clockfd does not refer to a valid EVL clock, or the generated name is badly formed.

    • -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 flag group.

    #include <evl/flags.h>
    
    static struct evl_flags flags;
    
    int create_new_flags(void)
    {
    	int fd;
    
    	fd = evl_create_flags(&flags, EVL_CLOCK_MONOTONIC, 0, "name_of_group");
    	/* skipping checks */
    	
    	return fd;
    }
    

    int evl_new_flags(struct evl_flags *flg, const char *fmt, ...)

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

    	evl_create_flags(flg, 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_FLAGS_INITIALIZER((const char *) name, (int) clockfd, (int) initval, (int) flags)

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


    int evl_open_flags(struct evl_flags *flg, const char *fmt, ...)

    You can open an existing flag group, possibly from a different process, by calling evl_open_flags().

  • flg

    An in-memory flag group descriptor is constructed by evl_open_flags(), which contains ancillary information other calls will need. flg is a pointer to such descriptor of type struct evl_flags. The information is retrieved from the existing flag group which was opened.

  • fmt

    A printf-like format string to generate the name of the flag group 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_flags() returns the file descriptor referring to the opened group on success, Otherwise, a negated error code is returned:

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

    • -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_wait_some_flags(struct evl_flags *flg, int bits, int *r_bits)

    This service waits for some flag(s) among the given set of bits to be posted or broadcast to the flag group.

    This form implements a disjunctive wait mode, in which the request is satisfied when (flg->value & bits) != 0. If the request can be satisfied immediately on entry to this service, the caller consumes the matched bits from the group’s value before returning without blocking, otherwise the caller blocked until this happens. Either way, the set of bits which satisfies the request is written back to r_bits and cleared from the group before the call returns successfully.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    A bit mask representing the set of bits which should be matched partially or entirely for the request to succeed. Passing zero or 0xffffffff is equivalent to matching any bit or combination thereof.

  • r_bits

    The optional address of an integer where the set of received flags which satisfied the request should be copied by the core. If NULL, the received flags are not copied back.

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

    -EINVAL flg does not represent a valid in-memory flag group 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 flg was statically initialized with EVL_FLAGS_INITIALIZER, then any error returned by evl_create_flags() may be passed back to the caller in case the implicit initialization call fails.


    int evl_wait_flags(struct evl_flags *flg, int *r_bits)

    This service is a shorthand for calling evl_wait_some_flags() with a bit matching set of 0xffffffff, meaning that any flag or combination thereof would satisfy the request.


    int evl_timedwait_some_flags(struct evl_flags *flg, int bits, const struct timespec *timeout, int *r_bits)

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

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    A bit mask representing the set of bits which should be matched partially or entirely for the request to succeed. Passing zero or 0xffffffff is equivalent to matching any bit or combination thereof.

  • timeout

    A time limit to wait for the caller to receive some flags before the call returns on error. The clock mentioned in the call to evl_create_flags() is used for tracking the elapsed time.

  • r_bits

    The optional address of an integer where the set of received flags which satisfied the request should be copied by the core. If NULL, the received flags are not copied back.

  • This call returns zero on success, with the received bits copied to r_bits. Otherwise, the following error code may be returned in addition to those mentioned for evl_wait_some_flags():

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


    int evl_timedwait_flags(struct evl_flags *flg, int *r_bits)

    This service is a shorthand for calling evl_timedwait_some_flags() with a bit matching set of 0xffffffff, meaning that any flag or combination thereof would satisfy the request.


    int evl_wait_exact_flags(struct evl_flags *flg, int bits)

    This service waits for an exact set of bits to be posted or broadcast to the flag group. Waiters are queued by order of scheduling priority.

    This form implements a conjunctive wait mode, in which the request is satisfied solely when (flg->value & bits) == bits. If the request can be satisfied immediately on entry to this service, the caller consumes the matched bits from the group’s value before returning without blocking, otherwise the caller is blocked until this happens. Either way, the set of bits which satisfies the request is cleared from the group before the call returns successfully.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    A bit mask representing the set of bits which should be fully matched for the request to succeed.

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

    -EINVAL flg does not represent a valid in-memory flag group 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 flg was statically initialized with EVL_FLAGS_INITIALIZER, then any error returned by evl_create_flags() may be passed back to the caller in case the implicit initialization call fails.


    int evl_timedwait_exact_flags(struct evl_flags *flg, int bits, const struct timespec *timeout)

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

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    A bit mask representing the set of bits which should be fully matched for the request to succeed.

  • timeout

    A time limit to wait for the caller to receive the flags before the call returns on error. The clock mentioned in the call to evl_create_flags() is used for tracking the elapsed time.

  • This call returns zero on success. Otherwise, the following error code may be returned in addition to those mentioned for evl_wait_exact_flags():

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


    int evl_trywait_some_flags(struct evl_flags *flg, int bits, int *r_bits)

    This service is a variant of evl_wait_some_flags() which attempts to consume any flag among the given set of bits if pending in the flag group, without blocking the caller if the request cannot be satisfied immediately. Otherwise, the set of bits which satisfies the request is cleared from the group before the call returns successfully.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    A bit mask representing the set of bits which should be matched partially or entirely for the request to succeed. Passing zero or 0xffffffff is equivalent to matching any bit or combination thereof.

  • r_bits

    The optional address of an integer where the set of received flags which satisfied the request should be copied by the core. If NULL, the received flags are not copied back.

  • evl_trywait_some_flags() returns zero on success. Otherwise, the following error codes may be returned:

    -EAGAIN The request could not be satisfied immediately.

    -EINVAL flg does not represent a valid in-memory flag group 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 flg was statically initialized with EVL_FLAGS_INITIALIZER, then any error returned by evl_create_flags() may be passed back to the caller in case the implicit initialization call fails.


    int evl_trywait_exact_flags(struct evl_flags *flg, int bits, int *r_bits)

    This service is a variant of evl_wait_exact_flags() which attempts to consume the exact set of flags given in the bits argument if pending in the flag group, without blocking the caller if the request cannot be satisfied immediately. Otherwise, the set of bits which satisfies the request is cleared from the group before the call returns successfully.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    A bit mask representing the set of bits which should be matched partially or entirely for the request to succeed. Passing zero or 0xffffffff is equivalent to matching any bit or combination thereof.

  • evl_trywait_exact_flags() returns zero on success. Otherwise, the following error codes may be returned:

    -EAGAIN The request could not be satisfied immediately.

    -EINVAL flg does not represent a valid in-memory flag group 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 flg was statically initialized with EVL_FLAGS_INITIALIZER, then any error returned by evl_create_flags() may be passed back to the caller in case the implicit initialization call fails.


    int evl_trywait_flags(struct evl_flags *flg, int *r_bits)

    This service is a shorthand for calling evl_trywait_some_flags() with a bit matching set of 0xffffffff, meaning that any flag or combination thereof would satisfy the request.


    int evl_post_flags(struct evl_flags *flg, int bits)

    This call posts a (non-null) set of flags to a flag group. The core unblocks the first waiter which gets its request satisfied by the added flags, by order of thread priority. Any undelivered bit is left pending in the group’s value.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() for flg is issued before the event mask is posted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    The (non-empty) set of flags to post to the flag group.

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

    -EINVAL flg does not represent a valid in-memory flag group 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.

    -EINVAL bits is zero.

    If flg was statically initialized with EVL_FLAGS_INITIALIZER but not passed to any flag group-related call yet, then any error status returned by evl_create_flags() may be passed back to the caller in case the implicit initialization call fails.


    int evl_broadcast_flags(struct evl_flags *flg, int bits)

    This call broadcasts a (non-null) set of flags to a flag group. The core unblocks ALL waiters which get their request satisfied by the added flags. In other words, broadcasting a set of flags to a flag group means posting a copy of that set individually to each thread found waiting on that group at the time of the call. Any undelivered bit is left pending in the group’s value.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, an implicit call to evl_create_flags() for flg is issued before the event mask is posted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    The (non-empty) set of flags to broadcast to the flag group.

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

    -EINVAL flg does not represent a valid in-memory flag group 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.

    -EINVAL bits is zero.

    If flg was statically initialized with EVL_FLAGS_INITIALIZER but not passed to any flag group-related call yet, then any error status returned by evl_create_flags() may be passed back to the caller in case the implicit initialization call fails.


    int evl_peek_flags(struct evl_flags *flg, int *r_bits)

    This call is a variant of evl_trywait_flags() which does not consume the flags before returning to the caller. In other words, the group value is not reset to zero before returning a non-zero set of pending events, allowing the group value to be read multiple times with no side-effect.

  • flg

    The in-memory flag group descriptor constructed by either evl_create_flags() or evl_open_flags(), or statically built with EVL_FLAGS_INITIALIZER. In the latter case, the flag group becomes valid for a call to evl_peek_flags() only after a post or [try]wait operation was issued for it.

  • r_bits

    The address of an integer which contains the group value on successful return from the call.

  • evl_peek_flags() returns zero on success along with the current group value. Otherwise, a negated error code may be returned if:

    -EINVAL flg does not represent a valid in-memory flag group 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_flags(struct evl_flags *flg)

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

  • flg

    The in-memory descriptor of the flag group to dismantle.

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

    -EINVAL flg does not represent a valid in-memory flag group 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 flag group descriptor which has never been used in wait or post operations always returns zero.


    Events pollable from an event flag group descriptor

    The evl_poll() interface can monitor the following events occurring on an event flag group descriptor:

    • POLLIN and POLLRDNORM are set whenever the flag group value is non-zero, which means that a subsequent attempt to wait for them might be successful without blocking, unless another thread managed to sneak in in the meantime and collected the pending flags.

    • POLLOUT and POLLWRNORM are set whenever the flag group value is zero, which means that no flag is pending at the time of the call. As a result, polling for such status waits for all pending bits to have been consumed by the receiving side.

    Using in-band I/O to access a flag group

    Since ABI 33, in-band threads - whether they are EVL-enabled or not - may post and receive events from a flag group using the regular write(2), read(2) and poll(2) system calls. In this form, a flag group is an easy-to-use, lightweight inter-stage IPC for synchronization, like cross-buffers are when it comes to exchanging data.

    read() an event flag group

    Reading an event flag group is implicitly a disjunctive wait. Any flag received would satisfy the request of an in-band reader, and all pending flags are atomically collected and cleared in the group when this happens. In other words, calling read(2) on the file descriptor returned by evl_create_flags() would have the same effect than calling evl_wait_flags() from an EVL-enabled thread.

    	#include <unistd.h>
    
    	/*
    	 * Reading the pending bits from `efd` which refers to
    	 * an EVL event flag group. Wait is disjunctive, unblocks
    	 * as soon as one or more bit are present, consumes
    	 * them all.
    	 */
    	int flags;
    	ssize_t ret = read(efd, &flags, sizeof(flags));
    

    Non-blocking I/O is enforced if O_NONBLOCK flag is set for the file descriptor, either by a file control operation, or by passing EVL_CLONE_NONBLOCK when creating the event flag group.

    write() an event flag group

    Writing to an event flag group using write(2) on the file descriptor always issues an unicast notification, waking up a single waiter for whom the request is satisfied. In other words, it has the same effect than calling evl_post_flags() from an EVL-enabled thread.

    	#include <unistd.h>
    
    	/*
    	 * Write 0xfeed to `efd` which refers to an EVL event
    	 * flag group, readying any pending waiter for whom the
    	 * request would be satisfied, out-of-band waiters first,
    	 * in-band waiters next.
    	 */
    	int flags = 0xfeed;
    	ssize_t ret = write(efd, &flags, sizeof(flags));
    

    poll() an event flag group

    You may do synchronous I/O multiplexing with the file descriptor returned by evl_create_flags() using the poll(2) service. The semantics are identical to those of its out-of-band counterpart.

    	#include <poll.h>
    
    	/*
    	 * Poll from `efd` which refers to an EVL event
    	 * flag group, until some flags are raised.
    	 */
    	struct pollfd pollfd;
    	int ret;
    	pollfd.fd = efd;
    	pollfd.events = POLLIN;
    	pollfd.revents = 0;
    	ret = poll(&pollfd, 1, -1);
    

    Last modified: Sat, 11 Nov 2023 12:18:03 +0100