Types, Send and Sync
The app
attribute injects a context, a collection of variables, into every
function. All these variables have predictable, non-anonymous types so you can
write plain functions that take them as arguments.
The API reference specifies how these types are generated from the input. You
can also generate documentation for you binary crate (cargo doc --bin <name>
);
in the documentation you'll find Context
structs (e.g. init::Context
and
idle::Context
).
The example below shows the different types generates by the app
attribute.
# #![allow(unused_variables)] #fn main() { //! examples/types.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::debug; use rtfm::{Exclusive, Instant}; #[rtfm::app(device = lm3s6965)] const APP: () = { static mut SHARED: u32 = 0; #[init(schedule = [foo], spawn = [foo])] fn init(c: init::Context) { let _: Instant = c.start; let _: rtfm::Peripherals = c.core; let _: lm3s6965::Peripherals = c.device; let _: init::Schedule = c.schedule; let _: init::Spawn = c.spawn; debug::exit(debug::EXIT_SUCCESS); } #[exception(schedule = [foo], spawn = [foo])] fn SVCall(c: SVCall::Context) { let _: Instant = c.start; let _: SVCall::Schedule = c.schedule; let _: SVCall::Spawn = c.spawn; } #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])] fn UART0(c: UART0::Context) { let _: Instant = c.start; let _: resources::SHARED = c.resources.SHARED; let _: UART0::Schedule = c.schedule; let _: UART0::Spawn = c.spawn; } #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] fn foo(c: foo::Context) { let _: Instant = c.scheduled; let _: Exclusive<u32> = c.resources.SHARED; let _: foo::Resources = c.resources; let _: foo::Schedule = c.schedule; let _: foo::Spawn = c.spawn; } extern "C" { fn UART1(); } }; #}
Send
Send
is a marker trait for "types that can be transferred across thread
boundaries", according to its definition in core
. In the context of RTFM the
Send
trait is only required where it's possible to transfer a value between
tasks that run at different priorities. This occurs in a few places: in message
passing, in shared static mut
resources and in the initialization of late
resources.
The app
attribute will enforce that Send
is implemented where required so
you don't need to worry much about it. It's more important to know where you do
not need the Send
trait: on types that are transferred between tasks that
run at the same priority. This occurs in two places: in message passing and in
shared static mut
resources.
The example below shows where a type that doesn't implement Send
can be used.
# #![allow(unused_variables)] #fn main() { //! `examples/not-send.rs` #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_halt; use core::marker::PhantomData; use cortex_m_semihosting::debug; use rtfm::app; pub struct NotSend { _0: PhantomData<*const ()>, } #[app(device = lm3s6965)] const APP: () = { static mut SHARED: Option<NotSend> = None; #[init(spawn = [baz, quux])] fn init(c: init::Context) { c.spawn.baz().unwrap(); c.spawn.quux().unwrap(); } #[task(spawn = [bar])] fn foo(c: foo::Context) { // scenario 1: message passed to task that runs at the same priority c.spawn.bar(NotSend { _0: PhantomData }).ok(); } #[task] fn bar(_: bar::Context, _x: NotSend) { // scenario 1 } #[task(priority = 2, resources = [SHARED])] fn baz(mut c: baz::Context) { // scenario 2: resource shared between tasks that run at the same priority *c.resources.SHARED = Some(NotSend { _0: PhantomData }); } #[task(priority = 2, resources = [SHARED])] fn quux(mut c: quux::Context) { // scenario 2 let _not_send = c.resources.SHARED.take().unwrap(); debug::exit(debug::EXIT_SUCCESS); } extern "C" { fn UART0(); fn UART1(); } }; #}
It's important to note that late initialization of resources is effectively a
send operation where the initial value is sent from idle
, which has the lowest
priority of 0
, to a task with will run with a priority greater than or equal
to 1
. Thus all late resources need to implement the Send
trait.
Sharing a resource with init
can be used to implement late initialization, see
example below. For that reason, resources shared with init
must also implement
the Send
trait.
# #![allow(unused_variables)] #fn main() { //! `examples/shared-with-init.rs` #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_halt; use cortex_m_semihosting::debug; use lm3s6965::Interrupt; use rtfm::app; pub struct MustBeSend; #[app(device = lm3s6965)] const APP: () = { static mut SHARED: Option<MustBeSend> = None; #[init(resources = [SHARED])] fn init(c: init::Context) { // this `message` will be sent to task `UART0` let message = MustBeSend; *c.resources.SHARED = Some(message); rtfm::pend(Interrupt::UART0); } #[interrupt(resources = [SHARED])] fn UART0(c: UART0::Context) { if let Some(message) = c.resources.SHARED.take() { // `message` has been received drop(message); debug::exit(debug::EXIT_SUCCESS); } } }; #}
Sync
Similarly, Sync
is a marker trait for "types for which it is safe to share
references between threads", according to its definition in core
. In the
context of RTFM the Sync
trait is only required where it's possible for two,
or more, tasks that run at different priority to hold a shared reference to a
resource. This only occurs with shared static
resources.
The app
attribute will enforce that Sync
is implemented where required but
it's important to know where the Sync
bound is not required: in static
resources shared between tasks that run at the same priority.
The example below shows where a type that doesn't implement Sync
can be used.
# #![allow(unused_variables)] #fn main() { //! `examples/not-sync.rs` #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_halt; use core::marker::PhantomData; use cortex_m_semihosting::debug; pub struct NotSync { _0: PhantomData<*const ()>, } #[rtfm::app(device = lm3s6965)] const APP: () = { static SHARED: NotSync = NotSync { _0: PhantomData }; #[init] fn init(_: init::Context) { debug::exit(debug::EXIT_SUCCESS); } #[task(resources = [SHARED])] fn foo(c: foo::Context) { let _: &NotSync = c.resources.SHARED; } #[task(resources = [SHARED])] fn bar(c: bar::Context) { let _: &NotSync = c.resources.SHARED; } extern "C" { fn UART0(); } }; #}