Программные задачи

RTFM обрабатывает прерывания и исключения как аппаратные задачи. Аппаратные задачи могут вызываться устройством в ответ на события, такие как нажатие кнопки. RTFM также поддерживает программные задачи, порождаемые программой из любого контекста выполнения.

Программным задачам также можно назначать приоритет и диспетчеризовать из обработчиков прерываний. RTFM требует определения свободных прерываний в блоке extern, когда используются программные задачи; эти свободные прерывания будут использованы, чтобы диспетчеризовать программные задачи. Преимущество программных задач перед аппаратными в том, что на один обработчик прерывания можно назначить множество задач.

Программные задачи определяются заданием функциям атрибута task. Чтобы было возможно вызывать программные задачи, имя задачи нужно передать в аргументе spawn контекста атрибута (init, idle, interrupt, etc.).

В примере ниже продемонстрированы три программных задачи, запускаемые на 2-х разных приоритетах. Трем задачам назначены 2 обработчика прерываний.


# #![allow(unused_variables)]
#fn main() {
//! examples/task.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init(c: init::Context) {
        c.spawn.foo().unwrap();
    }

    #[task(spawn = [bar, baz])]
    fn foo(c: foo::Context) {
        hprintln!("foo - start").unwrap();

        // spawns `bar` onto the task scheduler
        // `foo` and `bar` have the same priority so `bar` will not run until
        // after `foo` terminates
        c.spawn.bar().unwrap();

        hprintln!("foo - middle").unwrap();

        // spawns `baz` onto the task scheduler
        // `baz` has higher priority than `foo` so it immediately preempts `foo`
        c.spawn.baz().unwrap();

        hprintln!("foo - end").unwrap();
    }

    #[task]
    fn bar(_: bar::Context) {
        hprintln!("bar").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    #[task(priority = 2)]
    fn baz(_: baz::Context) {
        hprintln!("baz").unwrap();
    }

    // Interrupt handlers used to dispatch software tasks
    extern "C" {
        fn UART0();
        fn UART1();
    }
};

#}
$ cargo run --example task
foo - start
foo - middle
baz
foo - end
bar

Передача сообщений

Другое преимущество программных задач - возможность передавать сообщения задачам во время их вызова. Тип полезной нагрузки сообщения должен быть определен в сигнатуре обработчика задачи.

Пример ниже демонстрирует три задачи, две из которых ожидают сообщения.


# #![allow(unused_variables)]
#fn main() {
//! examples/message.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    #[init(spawn = [foo])]
    fn init(c: init::Context) {
        c.spawn.foo(/* no message */).unwrap();
    }

    #[task(spawn = [bar])]
    fn foo(c: foo::Context) {
        static mut COUNT: u32 = 0;

        hprintln!("foo").unwrap();

        c.spawn.bar(*COUNT).unwrap();
        *COUNT += 1;
    }

    #[task(spawn = [baz])]
    fn bar(c: bar::Context, x: u32) {
        hprintln!("bar({})", x).unwrap();

        c.spawn.baz(x + 1, x + 2).unwrap();
    }

    #[task(spawn = [foo])]
    fn baz(c: baz::Context, x: u32, y: u32) {
        hprintln!("baz({}, {})", x, y).unwrap();

        if x + y > 4 {
            debug::exit(debug::EXIT_SUCCESS);
        }

        c.spawn.foo().unwrap();
    }

    extern "C" {
        fn UART0();
    }
};

#}
$ cargo run --example message
foo
bar(0)
baz(1, 2)
foo
bar(1)
baz(2, 3)

Ёмкость

Диспетчеры задач не используют динамическое выделение памяти. Память необходимая для размещения сообщений, резервируется статически. Фреймворк зарезервирует достаточно памяти для каждого контекста, чтобы можно было вызвать каждую задачу как минимум единожды. Это разумно по умолчанию, но "внутреннюю" ёмкость каждой задачи можно контролировать используя аргумент capacity атрибута task.

В примере ниже установлена ёмкость программной задачи foo на 4. Если ёмкость не определена, тогда второй вызов spawn.foo в UART0 вызовет ошибку.


# #![allow(unused_variables)]
#fn main() {
//! examples/capacity.rs

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;

#[rtfm::app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init(_: init::Context) {
        rtfm::pend(Interrupt::UART0);
    }

    #[task(binds = UART0, spawn = [foo, bar])]
    fn uart0(c: uart0::Context) {
        c.spawn.foo(0).unwrap();
        c.spawn.foo(1).unwrap();
        c.spawn.foo(2).unwrap();
        c.spawn.foo(3).unwrap();

        c.spawn.bar().unwrap();
    }

    #[task(capacity = 4)]
    fn foo(_: foo::Context, x: u32) {
        hprintln!("foo({})", x).unwrap();
    }

    #[task]
    fn bar(_: bar::Context) {
        hprintln!("bar").unwrap();

        debug::exit(debug::EXIT_SUCCESS);
    }

    // Interrupt handlers used to dispatch software tasks
    extern "C" {
        fn UART1();
    }
};

#}
$ cargo run --example capacity
foo(0)
foo(1)
foo(2)
foo(3)
bar