logo
Expand description

The original kernel of R3-OS.

  • Traditional uniprocessor tickless real-time kernel with preemptive scheduling

  • Implements a software-based scheduler supporting a customizable number of task priorities (up to 2¹⁵ levels on a 32-bit target, though the implementation is heavily optimized for a smaller number of priorities) and an unlimited number of tasks.

  • Provides a scalable kernel timing mechanism with a logarithmic time complexity. This implementation is robust against a large interrupt processing delay.

  • The kernel is split into a target-independent portion and a target-specific portion. The target-specific portion (called a port) is provided as a separate crate. An application combines them using the trait system.

Configuring the Kernel

Kernel Trait Type

The R3 original kernel utilizes Rust’s trait system to allow system designers to combine necessary components of the kernel. Components are represented by traits implemented on an application-specific kernel trait type, often named SystemTraits by convention. The kernel trait type is then used as a parameter of r3_kernel::System<Traits> to form a concrete system type.

Rationale: “Trait” is an allusion to the type trait classes such as std::iterator_traits from the C++ standard library. You could also say that it’s named so because you implement various traits on it to configure the kernel, though.

Application code uses the following macros to build a kernel trait type in a modular way:

  • A port-provided macro like r3_xxx_port::use_port! (named in this way by convention) instantiates port-specfific items.
  • r3_kernel::build! instantiates the kernel and kernel-private static data based on the kernel configuration supplied in the form of a configuration function.
type System = r3_kernel::System<SystemTraits>;
r3_port_std::use_port!(unsafe struct SystemTraits);

struct Objects { /* ... */ }
const fn configure_app(_: &mut r3_kernel::Cfg<SystemTraits>) -> Objects
{ /* ... */ }

const COTTAGE: Objects = r3_kernel::build!(System, configure_app => Objects);

Trait Mechanics

The macros presented in the previous section generate many trait implementations interconnected under a complex relationship to the end of providing all necessary components. This section provides a detailed account of how this is implemented. An application developer is usually not required to understand this.

The following diagram outlines the trait relationship around a kernel trait type.

  • The arrows between traits represent a super-sub relationship. It’s not possible to create a circular dependency among them. Associated types can only be referenced in one direction.
  • The impls in the same crate as the implemented traits are blanket impls.
  • The arrows between impls represent trait bounds on Self (for blanket impls) or assumed impls (for the other impls). It’s not possible to create a circular dependency fully comprised of blanket impls, but otherwise it’s possible to create one.

traits

use_port! → System Type

The composition process revolves around a kernel trait type. The first thing to do is to define one. It could be defined by application code, but instead it’s defined by use_port! purely for convenience. A kernel trait type is named SystemTrait by convention.

r3_xxx_port::use_port!(unsafe struct SystemTrait);

// ----- The above macro invocation expands to: -----
struct SystemTrait;
/*  ⋮  */

use_port!impl Port

The first important role of use_port! is to implement the trait Port on the kernel trait type. Port describes the properties of the target hardware and provides target-dependent low-level functions such as a context switcher. use_port! can define static items to store internal state data (this would be inconvenient and messy without a macro).

Port is actually a group of several supertraits (such as PortThreading), each of which can be implemented in a separate location.

r3_xxx_port::use_port!(unsafe struct SystemTraits);

// ----- The above macro invocation also produces: -----
/*  ⋮  */
unsafe impl r3_kernel::PortThreading for SystemTraits { /* ... */ }
unsafe impl r3_kernel::PortInterrupts for SystemTraits { /* ... */ }
unsafe impl r3_kernel::PortTimer for SystemTraits { /* ... */ }
/*  ⋮  */

// `Port` gets implemented automatically when
// all required supertraits are implemented.

The job of use_port! doesn’t end here, but before we move on, we must first explain what build! does.

build!impl KernelCfgN

build! assembles a database of statically defined kernel objects using a supplied configuration function. Using this database, it does things such as determining the optimal data type to represent all allowed task priority values and defining static items to store kernel-private data structures such as task control blocks. The result is attached to a supplied kernel trait type by implementing KernelCfg1 and KernelCfg2 on it.

static COTTAGE: Objects =
    r3_kernel::build!(SystemTraits, configure_app => Objects);

// ----- The above macro invocation produces (simplified for brevity): -----
static COTTAGE: Objects = {
    use r3_kernel::TaskCb;

    const CFG: /* ... */ = {
        let mut raw_cfg = r3_kernel::cfg::CfgBuilder::new();
        let mut cfg = r3_core::kernel::cfg::Cfg::new(&mut raw_cfg);
        configure_app(&mut cfg);
        raw_cfg
    };

    static TASK_CB_POOL: [TaskCb<SystemTraits>; _] = /* ... */;

    // Things needed by both of `Port` and `KernelCfg2` should live in
    // `KernelCfg1` because `Port` cannot refer to an associated item defined
    // by `KernelCfg2`.
    unsafe impl r3_kernel::KernelCfg1 for SystemTraits {
        type TaskPriority = /* ... */;
    }

    // Things dependent on data types defined by `Port` should live in
    // `KernelCfg2`.
    unsafe impl r3_kernel::KernelCfg2 for SystemTraits {
        fn task_cb_pool() -> &'static [TaskCb<SystemTraits>] {
            &TASK_CB_POOL
        }
        /* ... */
    }

    // Make the generated object IDs available to the application
    configure_app(...)
};

impl KernelTraits

The traits introduced so far are enough to instantiate the target-independent portion of the RTOS kernel. To reflect this, KernelTraits and PortToKernel are automatically implemented on the kernel trait type by a blanket impl.

impl<System: Port + KernelCfg1 + KernelCfg2> KernelTraits for System { /* ... */ }
impl<System: KernelTraits> PortToKernel for System { /* ... */ }

use_port! → Entry Points

The remaining task of use_port! is to generate entry points to the kernel. The most important one is for booting the kernel. The other ones are interrupt handlers.

r3_xxx_port::use_port!(unsafe struct SystemTraits);

// ----- The above macro invocation lastly produces: -----
/*  ⋮  */
fn main() {
    <SystemTraits as r3_kernel::PortToKernel>::boot();
}

Implementation-Defined Behaviors

  • QueueOrder: This kernel supports Fifo and TaskPriority. Unsupported values are treated as TaskPriority.
  • MutexProtocol: This kernel supports None and Ceiling(_). Unsupported values are treated as None.
  • ResultCode::NoAccess: Not supported. This kernel causes an undefined behavior (including a potential panic) when an invalid ID is given.

Cargo Features

  • inline_syscall: Allows (but does not force) inlining for all application-facing methods. Enabling this feature might lower the latency of system calls but there are the following downsides: (1) The decision of inlining is driven by the compiler’s built-in heuristics, which takes many factors into consideration. Therefore, the performance improvement (or deterioration) varies unpredictably depending on the global structure of your application and the compiler version used, making it harder to design the system to meet real-time requirements. (2) Inlining increases the code working set size and can make the code run even slower. This is especially likely to happen on an execute-in-place (XIP) system with low-speed code memory such as an SPI flash.

Kernel Features

Enabling the following features might affect the kernel’s runtime performance and memory usage whether or not they are actually in use.

Modules

Changelog

Static configuration mechanism for the kernel

Utility

Macros

Structs

The object returned by <SystemasKernelBase>::debug. Implements fmt::Debug.

Hunk for a task stack.

Global kernel state.

Wraps a provided trait type Traits to instantiate a kernel. This type implements the traits from r3_core::kernel::raw, making it usable as a kernel, if Traits implements some appropriate traits, which consequently make it implement KernelTraits.

The static properties of a task.

Task control block - the state data of a task.

Constants

The extent of how overdue the firing of timer_tick can be without breaking the kernel timing algorithm.

The extent of how overdue a timed event can be made or how far a timed event can be delayed past Duration::MAX by a call to raw_adjust_time.

Traits

Associates a kernel trait type with kernel-private data. Use build! to implement.

Associates “system” types with kernel-private data. Use build! to implement.

Represents a complete kernel trait type.

Represents a particular group of traits that a port should implement.

Implemented by a port. This trait contains items related to controlling interrupt lines.

Implemented by a port. This trait contains items related to low-level operations for controlling CPU states and context switching.

Implemented by a port. This trait contains items related to controlling a system timer.

Methods intended to be called by a port.

Type Definitions

The instantiation of r3_core::kernel::Cfg used by build! to configure a kernel. CfgBuilder<...> in this alias Implements ~const raw_cfg::CfgBase<System<Traits>> and many other raw_cfg traits.

Numeric value used to identify various kinds of kernel objects.

Unsigned integer type representing a tick count used by a port timer driver. The period of each tick is fixed at one microsecond.