Part II: Kernel Architecture ############################ Architecture Overview ===================== Execution Model --------------- QuantumRT operates in two execution levels: Privileged and Unprivileged. Privileged code has unrestricted access to all system resources, while unprivileged code is restricted from modifying critical kernel or hardware registers. When Memory Protection is disabled, all code executes in privileged mode and can access the entire memory map. When Memory Protection is enabled, QuantumRT runs the kernel, idle thread, and all interrupt service routines in privileged mode. User threads may execute in unprivileged mode, isolating them from kernel memory and preventing direct access to protected peripherals. This separation increases system robustness by ensuring that application threads cannot corrupt kernel or other thread contexts. Memory Model ------------ Each thread has its own stack memory, isolated from other threads. The kernel and idle thread also have separate stacks. The kernel stack is used for kernel operations, while the idle thread stack is used when no other threads are ready to run. ARM Cortex-M processors implement Process Stack Pointer (PSP) and Main Stack Pointer (MSP) to facilitate stack separation. Threads (including idle thread) operate using PSP, while the kernel and ISRs utilize MSP. Boot & Kernel Bring-Up ====================== QuantumRT is shipped with default BSP for ARM Cortex-M platforms which handles necessary low-level initialization for the kernel operation. BSP must be initialized with :c:func:`bsp_init()`. System Timer must be configured with :c:func:`qrt_systimer_configure()` before starting the kernel. The kernel is initialized and started by calling :c:func:`qrt_kernelstart()`, which converts the currently executing code into the main thread. Main thread is created with default attributes and runs at the highest priority. After the kernel has started, the idle thread is created and the scheduler begins managing thread execution. Threads can only be created after the kernel has started. .. warning:: Main thread must not be returned from, as this would lead to undefined behavior, but instead should be exit with :c:func:`pthread_exit()`. Timers ====== System Timer ------------ The kernel requires a hardware timer to serve as the primary timekeeping mechanism. The System Timer provides time-based services, ensuring accurate delays, timeouts, and scheduling. The accuracy of timing services is directly dependent on the frequency of the hardware System Timer. The System Timer interrupt is configured with the highest priority to maintain accurate timing and ensure no ticks are lost during interrupt handling. To satisfy this requirement, the System Timer interrupt must be the only interrupt assigned the highest priority level. Interrupts ~~~~~~~~~~ System Timer interrupts occur whenever the timer reaches a scheduled deadline or when the deadline needs to be updated. On System Timer interrupt, the interrupt handler reloads the timer counter and signals the kernel that a deadline has expired. Deadline calculations for subsequent events are postponed until all Interrupt Service Routines (ISRs) have completed execution, minimizing interrupt latency. Configuration ~~~~~~~~~~~~~ The kernel requires integration with a hardware timer. The user must configure the hardware timer to trigger interrupts on overflow and call :c:func:`qrt_systimer_configure()`, providing: * A function for reading the hardware timer * A function for reloading the hardware timer * The counting direction of the hardware timer * The hardware timer's interrupt request number (IRQ) The hardware timer's interrupt handler must invoke :c:func:`qrt_systimer_handler()`. The kernel manages Nested Vectored Interrupt Controller (NVIC) configuration for the hardware timer. Base Timer ---------- Configuration ~~~~~~~~~~~~~ Precise Scheduling ------------------ The kernel implements Precise Scheduling, dynamically loading the System Timer with the next scheduled event's deadline instead of relying on fixed system ticks. This approach eliminates unnecessary timer interrupts, improving accuracy, reducing CPU overhead, and enhancing energy efficiency compared to traditional Tick-Based Scheduling. Scheduler ========= The scheduler manages thread execution, ensuring fair resource distribution, responsiveness, and prioritized handling of critical threads. The kernel uses preemptive scheduling, allowing it to interrupt a running thread whenever a higher priority thread becomes ready to execute. Priority-Based Scheduling -------------------------- The kernel uses preemptive priority-based scheduling, a scheduling algorithm designed to allocate processing time among threads based on their priority level, where a higher value represents higher priority. To enable Priority-Based scheduling for a thread, pthread attribute must be set with :c:macro:`SCHED_FIFO` with :c:func:`pthread_attr_setschedpolicy()`. Round-Robin Scheduling ----------------------- The kernel provides optional Round-Robin scheduling for threads of equal priority, ensuring fair distribution of CPU time among threads without strict real-time constraints, such as background tasks. Threads execute in rotation, each receiving a fixed execution time slice. When the time slice expires or a context switch occurs, the thread is returned to the ready queue, and another thread at the same priority level executes. However, the kernel does not guarantee a thread always completes its full time slice. To enable Round-Robin scheduling for a thread, pthread attribute must be set with :c:macro:`SCHED_RR` with :c:func:`pthread_attr_setschedpolicy()`. Round-Robin scheduling can be enabled with configuration option :c:macro:`QRT_CFG_SCHED_RR_ENABLE`. Interrupts and Scheduling -------------------------- Interrupt Service Routines (ISRs) respond to hardware events by temporarily interrupting thread execution. Efficient interrupt handling is essential for real-time responsiveness and deterministic scheduling. QuantumRT does not perform scheduling directly within ISR context. Instead, ISRs use deferred kernel calls to notify the kernel and interact with kernel objects once the ISR has completed. Scheduling decisions are made only after ISR execution finishes and deferred kernel calls have been processed, ensuring minimal interrupt latency and predictable execution. Yielding -------- Threads do not need to explicitly yield, but the scheduler automatically preempts lower-priority threads and manages execution order based on priority and scheduling policy. Threads can voluntarily yield the processor using :c:func:`sched_yield()`, allowing other threads of equal priority to execute. Yielding is particularly useful when a thread has completed its immediate work and wants to allow other threads to run. Priority Inversion ------------------- Priority inversion occurs when a lower-priority thread indirectly prevents a higher-priority thread from executing, effectively reversing the intended priority order. Priority inversion can be bounded or unbounded: * **Bounded priority inversion** occurs when a high-priority thread is waiting for a resource held by a low-priority thread. The duration of inversion is bounded because it ends as soon as the low-priority thread releases the resource. * **Unbounded priority inversion** occurs if the duration of inversion becomes unpredictable due to intermediate-priority threads preempting the low-priority thread holding a critical resource needed by a high-priority thread. Idle Thread ----------- The kernel maintains an idle thread that runs when no other threads are in the ready state. The idle thread ensures that the processor always has a runnable thread, even if no application-specific tasks are ready to execute. Typically, the idle thread executes continuously at the lowest priority level. It runs in a minimal infinite loop, performing power-saving operations or entering a low-power state to conserve energy when the system is idle if configured so. Power save mode can be enabled with configuration option :c:macro:`QRT_CFG_IDLE_DEEP_SLEEP_ENABLE`. Floating-Point Support ====================== Some supported processor cores include a FPU (Floating-Point Unit), introducing additional registers that must be managed during context switching. Stacking floating-point registers increases thread stack size, adds interrupt latency, and extends context switching time because the registers must be saved and restored. The kernel supports stacking floating-point registers and it can be enabled per thread with call to :c:func:`qrt_fpu_ctxenable()` and can be disabled with :c:func:`qrt_fpu_ctxdisable()`. FPU support can be enabled with configuration option :c:macro:`QRT_CFG_FPU_ENABLE`, and floating-point stacking can be enabled with :c:macro:`QRT_CFG_FPU_STACKING_ENABLE`.