Real-Time Interrupt-driven Concurrency (RTIC)

repository·master·Indexed 25 days ago

https://github.com/rtic-rs/rtic

A hardware-accelerated Rust RTOS and concurrency framework for building real-time systems. Version 2.3.0 provides compile-time guarantees against deadlocks and data races with minimal overhead. The framework supports various platforms including STM32, ESP32-C3, ESP32-C6, nRF52840, and EFR32, featuring support for hardware and software tasks, MPSC channels for task communication, and local/shared resource management.

Tokens
29.7K
Snippets
58
Records
173
Agent score
81%

What's inside rtic

  1. Overview of RTIC (Real-Time Interrupt-driven Concurrency)

    master

    RTIC is a concurrency framework designed for building real-time systems. It is also known as 'Real-Time For the Masses'. It provides a highly efficient, deadlock-free environment for embedded systems, specifically targeting Cortex-M devices, though it is a general concurrency framework.

    Key features include:

    • Tasks: The unit of concurrency. Tasks can be event-driven (triggered by asynchronous events) or software-triggered.
    • Message Passing: Allows passing messages between tasks, specifically at the moment of task invocation.
    • Timer Queue: Enables scheduling tasks to run at a specific point in the future, useful for periodic tasks.
    • Preemptive Multitasking: Supported via task priorities.
    • Resource Sharing: Efficient, race-condition-free resource sharing using priority-based critical sections.
    • Deadlock-free Execution: Guaranteed at compile time, providing stronger guarantees than a standard Mutex.
    • Minimal Overhead: Task dispatching is primarily handled by hardware, resulting in a minimal software footprint.
    • Memory Efficiency: All tasks share a single call stack, and there is no heavy dependency on a dynamic allocator.
  2. What is RTIC (Real-Time Interrupt-driven Concurrency)?

    master

    RTIC is a hardware-accelerated concurrency framework for building real-time systems in Rust. It provides a model for managing tasks, prioritization, and memory sharing with minimal software overhead by leveraging hardware features for scheduling.

    Key characteristics include:

    • Deadlock-free execution: Guaranteed at compile time.
    • Efficient memory usage: All tasks share a single call stack, and there is no hard dependency on a dynamic memory allocator.
    • Preemptive multitasking: Supported through task prioritization.
    • Data race free memory sharing: Achieved through fine-grained, priority-based critical sections.
  3. What is RTIC?

    master

    RTIC (Real-Time Interrupt-driven Concurrency) is a concurrency framework for building real-time systems. It is often described as a hardware-accelerated RTOS because it leverages hardware interrupt controllers (like the NVIC on Cortex-M or CLIC on RISC-V) to perform scheduling instead of using a traditional software kernel.

    Key characteristics:

    • Hardware Accelerated: Uses existing hardware interrupt mechanisms for scheduling.
    • Concurrency Framework: Relies on external Hardware Abstraction Layers (HALs) and has no software kernel.
    • SRP-based: Built on the Stack Resource Policy (SRP) for efficient, deadlock-free resource management.
  4. Use divergent tasks for infinite loops

    master

    A software task can be defined with a divergent return type: async fn task_name(cx: task_name::Context, ...) -> !.

    Advantages of divergent tasks (-> !):

    • Static Lifetimes: They receive a 'static context, and local resources have a 'static lifetime.
    • Explicit Intent: It clearly distinguishes tasks intended to run indefinitely from short-lived tasks.

    Warning: You must ensure the task yields control using .await to avoid starving other tasks at the same priority level.

  5. Resource management pattern in the STM32F411 ADC example

    master

    This example demonstrates how to manage hardware resources in RTIC using local and shared resource patterns:

    • Local Resources: The Potentiometer struct (holding the analog pin PA1) is assigned as a local resource to the EXTI0 hardware task. Local resources are owned exclusively by a single task, providing efficient, lock-free access.
    • Shared Resources: The Adc<ADC1> instance is assigned as a shared resource. This allows multiple tasks to access the single ADC module present on the microcontroller, requiring RTIC's resource management (locks) to ensure safe concurrent access.
  6. Compare RTIC with traditional RTOS

    master

    When choosing between RTIC and a traditional Real-Time Operating System (RTOS), consider the following differences in safety and security:

    Traditional RTOS

    • Safety Guarantees: Typically provides no guarantees regarding system-wide safety. Even formally verified kernels (like seL4) primarily guarantee the integrity of the kernel itself, not the entire system.
    • Resource Management: Often relies on dynamic allocation of resources, which introduces risks related to allocation failures and requires the application to correctly manage de-allocations.
    • Security: Cannot inherently guarantee confidentiality or integrity, as these depend on the security-critical code being trusted.

    RTIC

    • Safety and Security by Design: Leverages Rust's ownership and type system (compile-time aliasing, mutability, and lifetime guarantees) to propagate safety properties to the system-wide model.
    • Resource Control: The declarative model provides precise, static control over which tasks can access which shared resources.
    • Predictability: By avoiding dynamic allocation, RTIC eliminates a common class of runtime failures found in traditional OSs.
  7. How software tasks and spawn work

    master

    A software task in RTIC is an async fn that is not explicitly bound to a hardware interrupt vector. Instead, it is bound to a "dispatcher" interrupt vector running at the task's intended priority.

    To declare a software task, use the #[task] attribute without the binds = InterruptName argument. You can start a software task using the static method task_name::spawn().

    Key characteristics:

    • Async/Await: Software tasks are async, allowing you to await future events, blending reactive hardware tasks with sequential logic.
    • Execution: If no higher priority tasks are running, the task executes immediately upon spawning.
    • Lifecycle: Unlike hardware tasks which are run-to-completion, software tasks can run forever as long as they contain at least one await (yielding operation) to prevent starving other tasks at the same priority level.
    • Re-spawning: You can spawn a task again only after it has run-to-completion (returned). Attempting to spawn a task that is already running will result in an error.
  8. How the `spawn` implementation changes when using `schedule`

    master

    When the schedule API is used in RTIC, the internal implementation of spawn is modified to track task baselines. The system uses an INSTANTS buffer to store the specific time at which a task was scheduled to run.

    This Instant is captured during the spawn process and is subsequently passed to the task dispatcher. The dispatcher reads this value from the buffer and provides it to the user-defined task code as part of the task's Context.

  9. How the RTIC Timer Queue and SysTick work together

    master

    The timer queue is a priority queue (implemented as a min-heap) that stores tasks sorted by their earliest scheduled time.

    1. Scheduling: When schedule is called, the task and its execution Instant are added to the TimerQueue. If the new task is now the earliest, the SysTick interrupt is immediately pended.
    2. SysTick Handler: The SysTick interrupt performs two roles:
      • It dequeues tasks from the TimerQueue that have reached their scheduled time and moves them into their respective ready queues.
      • It sets up a new timeout interrupt to fire when the next task in the queue is due.
    3. Task Execution: Once a task is in a ready queue, the task dispatcher is triggered to run it at its assigned priority.
  10. Use `rtic-sync` for async message passing and resource sharing

    master

    When working in an async context within RTIC, you can use the rtic-sync crate to handle communication and shared state without relying on traditional blocking lock mechanisms.

    Key primitives include:

    • Arbiter: Allows you to await access to a shared resource in an async context. This provides a way to manage resource contention without using the standard RTIC lock method, which is typically used in non-async tasks.
    • Channel: Enables message passing between tasks. It is compatible with both async and non-async tasks, making it a versatile tool for inter-task communication.
  11. How to implement infinite software tasks

    master

    RTIC v2 async software tasks are permitted to run forever (e.g., using a loop {}). However, you must satisfy one precondition: there must be an .await point within the infinite loop. This ensures the task yields control and allows the executor to run other tasks.

    Commonly, this is achieved by awaiting a value from a channel or a timer.

    #[task(local = [ my_channel ] )]
    async fn my_task_that_runs_forever(cx: my_task_that_runs_forever::Context) {
        loop {
            let value = cx.local.my_channel.recv().await;
            do_something_with_value(value);
        }
    }