QP/C++  7.3.3
Real-Time Embedded Framework
No Matches
Preemptive Dual-Mode Kernel

Preemptive Run-to-Completion KernelQuality Attributes

Theory of Operation

QXK is a small, preemptive, priority-based, dual-mode blocking kernel that executes active objects like the QK kernel (basic threads), but can also execute traditional blocking threads (extended threads). In this respect, QXK behaves exactly like a conventional RTOS (Real-Time Operating System).

QXK has been designed specifically for mixing event-driven active objects with traditional blocking code, such as commercial middleware (TCP/IP stacks, UDP stacks, embedded file systems, etc.) or legacy software. To this end, QXK is not only more efficient than running QP on top of a traditional 3rd-party RTOS (because non-blocking basic threads take far less stack space and CPU cycles for context switch than the much heavier extended threads). But the biggest advantage of QXK is that it protects the application-level code from inadvertent mixing of blocking calls inside the event-driven active objects. Specifically, QXK "knows" the type of the thread context (extended/basic) and asserts internally if a blocking call (e.g., semaphore-wait or a time-delay) is attempted in a basic thread (active object). This is something that a QP port to a conventional 3rd-party RTOS cannot do, because such an RTOS runs all code (including active objects) in the context of heavyweight extended threads.

Currently, the QXK kernel has been ported to the following CPUs:

Basic Threads

QXK supports basic-threads (non-blocking, run-to-completion activations). The basic-threads all nest on the same stack (Main Stack Pointer in ARM Cortex-M), so the stack usage is reduced. The additional advantage of basic-threads is that switching from basic-thread to another basic-thread requires only activation of the basic-thread, which is much simpler and faster than full context-switch required for extended-threads that QXK also supports (see below).

QXK adopts the "basic/extended thread" terms from the OSEK/AUTOSAR Operating System specification. Other real-time kernels might use different terminology for similar concepts. For example, the Q-Kernel uses the term "fibers", while TI-RTOS uses the term "software interrupts" for concepts closely related to "basic threads".

Extended Threads

QXK supports extended-threads (blocking, typically structured as endless loops). The extended-threads use private per-thread stacks, as in conventional RTOS kernels. Any switching from basic-to-extended thread or extended-to-extended thread requires full context switch.

QXK is a unique dual-mode kernel on the market that supports interleaving the priorities of basic threads and extended threads. Other dual-mode kernels typically limit the priorities of basic threads to be always higher (more urgent) than any of the extended threads.
See also

QXK Feature Summary

As you can see in the list below, QXK provides most features you might expect of a traditional blocking RTOS kernel and is recommended as the preferred RTOS kernel for QP applications that need to mix active objects with traditional blocking code.

  • Preemptive, priority-based scheduling of up to 64 threads. Each thread must be assigned its own unique priority (1 .. QF_MAX_ACTIVE);
QXK always executes the highest-priority thread that is ready to run (is not blocked). The scheduling algorithm used in QXK meets all the requirement of the Rate Monotonic Scheduling (a.k.a. Rate Monotonic Analysis — RMA) and can be used in hard real-time systems.
  • QXK distinguishes between two types of threads:
  • basic threads of active objects that are made ready-to-run by events posted to the active objects. Such basic threads are non-blocking, run-to-completion activations that cannot block in the middle of the RTC step. QXK asserts when a basic thread attempts to use a blocking mechanism, such as a time-delay or a semaphore-wait. All basic threads share the common stack.
  • extended threads that can block anywhere in their thread-handler function, whereas QXK provides a typical assortments of blocking primitives for extended-threads, such as time-delay, blocking message queue, counting semaphore, or a mutex. Each extended thread must be provided with its own private stack.
  • Tightly integrated mechanisms for communication between event-driven active objects and extended blocking threads:
  • Basic threads (Active Objects) can signal semaphores and send messages to extended threads.
  • Extended threads can post or publish events to active objects or other extended threads;
  • Extended threads can subscribe to events and thus can receive events published in the system.
  • Priority-Ceiling, recursive Mutexes with optional timeout;
Priority-ceiling protocol implemented in QXK is immune to priority-inversions, but requires a unique QP thread priority level (the ceiling priority) to be assigned to the mutex. This ceiling priority is unavailable to QP threads.
A QXK mutex can be configured not to use the priority-ceiling protocol (when initialized with a zero priority-ceiling). In that case, the mutex does not require a separate priority level.
  • Counting Semaphores with optional timeout that can block multiple extended-threads;
  • Blocking "zero-copy" message queue with optional timeout bound to each extended-thread;
  • Deterministic fixed-size memory pools for dynamic memory management available both to extended-threads and active objects;
  • Interrupt management, including "zero-latency", kernel-unaware interrupts that are never disabled;
This feature is only supported on CPUs that allow selective interrupt disabling, such as ARM Cortex-M3/M4 (but not ARM Cortex-M0/M0+);

Scheduler Locking

Thread Local Storage

Thread-local storage (TLS) is a programming method that uses static or global memory local to a thread. TLS is specifically useful for writing library-type code, which is used in a multithreaded environment and needs to access per-thread data in an independent way.

TLS is used in some places where ordinary, single-threaded programs would use static or global variables, but where this would be inappropriate in multithreaded cases. An example of such situations is where library-type functions use a global variable to set an error condition (for example the global variable errno used by many functions of the C library). If errno were simply a global variable, a call of a system function on one thread may overwrite the value previously set by a call of a system function on a different thread, possibly before following code on that different thread could check for the error condition. The solution is to have errno be a variable that looks like it is global, but in fact exists once per thread—i.e., it lives in thread-local storage. A second use case would be multiple threads accumulating information into a global variable. To avoid a race condition, every access to this global variable would have to be protected by a mutual-exclusion mechanism. Alternatively, each thread might accumulate into a thread-local variable (that, by definition, cannot be read from or written to from other threads, implying that there can be no race conditions). Threads then only have to synchronize a final accumulation from their own thread-local variable into a single, truly global variable.

The TLS implementations vary, but many systems, including QXK, implement TLS by providing a pointer-sized variable thread-local. This pointer can be set to arbitrarily sized memory blocks in a thread-local manner, by allocating such a memory block (statically or dynamically) and storing the memory address of that block in the thread-local variable.

Typical usage of TLS in QXK is illustrated in the example qpc/examples/arm-cm/dpp_efm32-slstk3401a/qxk/, test.c, and consists:

  • define the TLS structure

typedef struct {
uint32_t foo;
uint8_t bar[10];
} TLS_test;
  • allocate the TLS storage for all participating threads (extended or basic threads)

static TLS_test l_tls1;
static TLS_test l_tls2;
  • initialize the TLS per-thread pointer in each thread routine (for extended threads) or the top-most initial transition (for basic threads of active objects):
    static void Thread1_run(QXThread * const me) {
    me->super.thread = &l_tls1; /* initialize the TLS for Thread1 */
    . . .
    . . .
    static void Thread2_run(QXThread * const me) {
    me->super.thread = &l_tls2; /* initialize the TLS for Thread2 */
    . . .
  • access the TLS from your code:

void lib_fun(uint32_t x) {
QXK_TLS(TLS_test *)->foo = x;
See also
  • QXK_current()
  • QXK_TLS()


Preemptive Run-to-Completion KernelQuality Attributes