1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

sys/event: Allow shared thread for multiple queues

Allow using `event_loop_multi()` to handle event queues of multiple priorities
in an single thread. In the extreme case, all three event queues are handled
by a single thread (thus saving two stacks). This comes for the price of
increased worst case latency, as already running event handlers will no longer
be preempted by higher priority events.

With this, all three event queue priorities are always provided. Using modules,
the old behavior of one thread per event queue can be restored for better worst
case latency at the expense of additional thread size.
This commit is contained in:
Marian Buschsieweke 2020-11-18 20:48:16 +01:00
parent 955b1f5be8
commit c6211cc6c2
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
3 changed files with 138 additions and 63 deletions

View File

@ -21,21 +21,31 @@
* @}
*/
#include "architecture.h"
#include "thread.h"
#include "event.h"
#include "event/thread.h"
static void *_handler(void *event_queue)
struct event_queue_and_size {
event_queue_t *q;
size_t q_numof;
};
static void *_handler_thread(void *tagged_ptr)
{
event_queue_claim(event_queue);
event_loop(event_queue);
event_queue_t *qs = ptrtag_ptr(tagged_ptr);
/* number of queues is encoded in lower pointer bits */
size_t n = ptrtag_tag(tagged_ptr) + 1;
event_queues_claim(qs, n);
/* start event loop */
event_loop_multi(qs, n);
/* should be never reached */
return NULL;
}
void event_thread_init(event_queue_t *queue, char *stack, size_t stack_size,
unsigned priority)
void event_thread_init_multi(event_queue_t *queues, size_t queues_numof,
char *stack, size_t stack_size, unsigned priority)
{
/* For the auto_init use case, this will be called before main gets
* started. main might already use the queues, so they need to be
@ -43,9 +53,12 @@ void event_thread_init(event_queue_t *queue, char *stack, size_t stack_size,
*
* They will be claimed within the handler thread.
*/
event_queue_init_detached(queue);
event_queues_init_detached(queues, queues_numof);
thread_create(stack, stack_size, priority, 0, _handler, queue, "event");
void *tagged_ptr = ptrtag(queues, queues_numof - 1);
thread_create(stack, stack_size, priority, 0, _handler_thread, tagged_ptr,
"event");
}
#ifndef EVENT_THREAD_STACKSIZE_DEFAULT
@ -78,48 +91,43 @@ void event_thread_init(event_queue_t *queue, char *stack, size_t stack_size,
#define EVENT_THREAD_LOWEST_PRIO (THREAD_PRIORITY_IDLE - 1)
#endif
#ifdef MODULE_EVENT_THREAD_HIGHEST
event_queue_t event_queue_highest;
static char _evq_highest_stack[EVENT_THREAD_HIGHEST_STACKSIZE];
#endif
/* rely on compiler / linker to garbage collect unused stacks */
static char WORD_ALIGNED _evq_highest_stack[EVENT_THREAD_HIGHEST_STACKSIZE];
static char WORD_ALIGNED _evq_medium_stack[EVENT_THREAD_MEDIUM_STACKSIZE];
static char WORD_ALIGNED _evq_lowest_stack[EVENT_THREAD_LOWEST_STACKSIZE];
#ifdef MODULE_EVENT_THREAD_MEDIUM
event_queue_t event_queue_medium;
static char _evq_medium_stack[EVENT_THREAD_MEDIUM_STACKSIZE];
#endif
#ifdef MODULE_EVENT_THREAD_LOWEST
event_queue_t event_queue_lowest;
static char _evq_lowest_stack[EVENT_THREAD_LOWEST_STACKSIZE];
#endif
typedef struct {
event_queue_t *queue;
char *stack;
size_t stack_size;
unsigned priority;
} event_threads_t;
const event_threads_t _event_threads[] = {
#ifdef MODULE_EVENT_THREAD_HIGHEST
{ &event_queue_highest, _evq_highest_stack, sizeof(_evq_highest_stack),
EVENT_THREAD_HIGHEST_PRIO },
#endif
#ifdef MODULE_EVENT_THREAD_MEDIUM
{ &event_queue_medium, _evq_medium_stack, sizeof(_evq_medium_stack),
EVENT_THREAD_MEDIUM_PRIO },
#endif
#ifdef MODULE_EVENT_THREAD_LOWEST
{ &event_queue_lowest, _evq_lowest_stack, sizeof(_evq_lowest_stack),
EVENT_THREAD_LOWEST_PRIO },
#endif
};
event_queue_t event_thread_queues[EVENT_QUEUE_PRIO_NUMOF];
void auto_init_event_thread(void)
{
for (unsigned i = 0; i < ARRAY_SIZE(_event_threads); i++) {
event_thread_init(_event_threads[i].queue,
_event_threads[i].stack, _event_threads[i].stack_size,
_event_threads[i].priority);
if (IS_USED(MODULE_EVENT_THREAD_HIGHEST)) {
/* In order to allow highest priority events to preempt all others,
* high priority events must be run in their own thread. This thread
* can preempt than preempt the other event thread(s). */
event_thread_init(EVENT_PRIO_HIGHEST,
_evq_highest_stack, sizeof(_evq_highest_stack),
EVENT_THREAD_HIGHEST_PRIO);
}
if (IS_USED(MODULE_EVENT_THREAD_MEDIUM)) {
/* In order to allow medium priority events to preempt low priority
* events, we need to move the low priority events into their own
* thread. The always existing medium priority event thread can then
* preempt the lowest priority event thread. */
event_thread_init(EVENT_PRIO_LOWEST,
_evq_lowest_stack, sizeof(_evq_lowest_stack),
EVENT_THREAD_LOWEST_PRIO);
}
event_queue_t *qs = EVENT_PRIO_MEDIUM;
size_t qs_numof = 1;
if (!IS_USED(MODULE_EVENT_THREAD_HIGHEST)) {
qs = EVENT_PRIO_HIGHEST;
qs_numof = 2;
}
if (!IS_USED(MODULE_EVENT_THREAD_MEDIUM)) {
qs_numof++;
}
event_thread_init_multi(qs, qs_numof,
_evq_medium_stack, sizeof(_evq_medium_stack),
EVENT_THREAD_MEDIUM_PRIO);
}

View File

@ -104,6 +104,7 @@
#include "irq.h"
#include "thread.h"
#include "thread_flags.h"
#include "ptrtag.h"
#ifdef __cplusplus
extern "C" {
@ -147,12 +148,11 @@ struct event {
/**
* @brief event queue structure
*/
typedef struct {
typedef struct PTRTAG {
clist_node_t event_list; /**< list of queued events */
thread_t *waiter; /**< thread ownning event queue */
thread_t *waiter; /**< thread owning event queue */
} event_queue_t;
/**
* @brief Initialize an array of event queues
*

View File

@ -12,6 +12,39 @@
* @ingroup sys_event
* @brief Provides utility functions for event handler threads
*
* Usage
* =====
*
* By using the module `event_thread`, the event queues
* @ref EVENT_PRIO_HIGHEST, @ref EVENT_PRIO_MEDIUM, and
* @ref EVENT_PRIO_LOWEST are provided and declared in the header
* `event/thread.h`. With default settings, the `auto_init` module will
* automatically start one or more threads to handle these
* queues.
*
* By default, a single thread with priority `EVENT_THREAD_MEDIUM_PRIO`
* will handle all three event queues according to their priority.
* An already started event handler will not be preempted in this case by an
* incoming higher priority event. Still, lower priority event queues will only
* be worked on once the higher priority queues are empty. Hence, the worst case
* latency is increased by the worst case duration of any possible lower
* priority event in this configuration.
*
* By using module `event_thread_highest`, the highest priority queue gets its
* own thread. With this, events of the highest priority can preempt already
* running event handlers of medium and lowest priority.
*
* By using module `event_thread_medium`, the lowest priority events are handled
* in their own thread. With this, events of at least medium priority can
* preempt already running events of the lowest priority.
*
* By using both module `event_thread_highest` and `event_thread_medium`, each
* event queue gets its own thread. So higher priority events will always
* preempt events of lower priority in this case.
*
* Finally, the module `event_thread_lowest` is provided for backward
* compatibility and has no effect.
*
* @{
*
* @file
@ -31,6 +64,22 @@
extern "C" {
#endif
/**
* @brief Convenience function for initializing an event queue thread
* handling multiple queues
*
* @param[in] queues array of the preallocated queue objects
* @param[in] queues_numof number of elements in @p queues
* @param[in] stack ptr to stack space
* @param[in] stack_size size of stack
* @param[in] priority priority to use
*
* @pre @p queues_numof is at most 4
*/
void event_thread_init_multi(event_queue_t *queues, size_t queues_numof,
char *stack, size_t stack_size,
unsigned priority);
/**
* @brief Convenience function for initializing an event queue thread
*
@ -39,23 +88,41 @@ extern "C" {
* @param[in] stack_size size of stack
* @param[in] priority priority to use
*/
void event_thread_init(event_queue_t *queue, char *stack, size_t stack_size,
unsigned priority);
static inline void event_thread_init(event_queue_t *queue,
char *stack, size_t stack_size,
unsigned priority)
{
event_thread_init_multi(queue, 1, stack, stack_size, priority);
}
#ifdef MODULE_EVENT_THREAD_HIGHEST
extern event_queue_t event_queue_highest;
#define EVENT_PRIO_HIGHEST (&event_queue_highest)
#endif
/**
* @brief Event queue priorities
*
* @details The lower the numeric value, the higher the priority. The highest
* priority is 0, so that these priorities can be used as index to
* access arrays.
*/
enum {
EVENT_QUEUE_PRIO_HIGHEST, /**< Highest event queue priority */
EVENT_QUEUE_PRIO_MEDIUM, /**< Medium event queue priority */
EVENT_QUEUE_PRIO_LOWEST, /**< Lowest event queue priority */
EVENT_QUEUE_PRIO_NUMOF /**< Number of event queue priorities */
};
#ifdef MODULE_EVENT_THREAD_MEDIUM
extern event_queue_t event_queue_medium;
#define EVENT_PRIO_MEDIUM (&event_queue_medium)
#endif
extern event_queue_t event_thread_queues[EVENT_QUEUE_PRIO_NUMOF];
#ifdef MODULE_EVENT_THREAD_LOWEST
extern event_queue_t event_queue_lowest;
#define EVENT_PRIO_LOWEST (&event_queue_lowest)
#endif
/**
* @brief Pointer to the event queue handling highest priority events
*/
#define EVENT_PRIO_HIGHEST (&event_thread_queues[EVENT_QUEUE_PRIO_HIGHEST])
/**
* @brief Pointer to the event queue handling medium priority events
*/
#define EVENT_PRIO_MEDIUM (&event_thread_queues[EVENT_QUEUE_PRIO_MEDIUM])
/**
* @brief Pointer to the event queue handling lowest priority events
*/
#define EVENT_PRIO_LOWEST (&event_thread_queues[EVENT_QUEUE_PRIO_LOWEST])
#ifdef __cplusplus
}