-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Currently RTFM has the notion of asymmetric access to shared resources. Tasks with highest priority accessing shared:T
gets a &mut T
API, while other tasks (if any) gets a resource proxy, with a lock
API (former claim
). The examples/lock.rs
from the current upstream.
#[rtfm::app(device = lm3s6965)]
const APP: () = {
struct Resources {
#[init(0)]
shared: u32,
}
#[init]
fn init(_: init::Context) {
rtfm::pend(Interrupt::GPIOA);
}
// when omitted priority is assumed to be `1`
#[task(binds = GPIOA, resources = [shared])]
fn gpioa(mut c: gpioa::Context) {
hprintln!("A").unwrap();
// the lower priority task requires a critical section to access the data
c.resources.shared.lock(|shared| {
// data can only be modified within this critical section (closure)
*shared += 1;
// GPIOB will *not* run right now due to the critical section
rtfm::pend(Interrupt::GPIOB);
hprintln!("B - shared = {}", *shared).unwrap();
// GPIOC does not contend for `shared` so it's allowed to run now
rtfm::pend(Interrupt::GPIOC);
});
// critical section is over: GPIOB can now start
hprintln!("E").unwrap();
debug::exit(debug::EXIT_SUCCESS);
}
#[task(binds = GPIOB, priority = 2, resources = [shared])]
fn gpiob(c: gpiob::Context) {
// the higher priority task does *not* need a critical section
*c.resources.shared += 1;
hprintln!("D - shared = {}", *c.resources.shared).unwrap();
}
#[task(binds = GPIOC, priority = 3)]
fn gpioc(_: gpioc::Context) {
hprintln!("C").unwrap();
}
};
This is unfortunate, as
- Introducing another task in the application may break code.
gpiob
would need to be changed API if allowinggpioc
access toshared
. - If we want
gpioa
to pass the resource access to another (external) function (like a HAL), we would need to wrapshared
byExclusive
(which gives alock
API to inner). Context
cannot have'static
lifetime if allowed to have&mut T
inresources
. This will prevent from captures in static stored generators (which requires'static
lifetime). Generators can be useful as is (initial experiments confirm the possibility), and are used under the hood forasync/await
, so if we want to keep this door open, we will eventually run into this problem.
Proposal.
Offer only a single API (always resource proxy) for all shared resources.
Pros:
- Symmetrical API with no risk of breakage.
- Mitigates the need for
Exclusive
. - Potentially reducing the complexity of
codegen
internally in RTFM. (Right now there a bunch of edge cases in thecodegen
).
Cons:
- More verbose user code, the price we pay for ensuring composition property. With the new multi-lock Mutex (korken), we can reduce rightward drift.
Performance:
- Locks will be optimized out by the compiler in case your task has exclusive access.
[resource] vs [mut resource] (or [&resource] vs [resource])
In a related RFC, Jorge proposed to distinguish between mutable and immutable access. With the above proposal, this will amount in a slight syntax extension. (Following Rust I would think that [mut resource] would be appropriate, but the &syntax would be backwards compatible, so I don't have a strong opinion).
Pros:
- Improved schedulability (less blocking from lower priority tasks)
- Improved performance (unnecessary locks are optimized out at compile time)
Cons:
- None.
Under the "Goodby Exclusive" proposal, the optimization can be done fully transparent to the API. lock
will lend out &T
/&mut T
, for immutable/mutable access respectively and the compiler will block misuse.
I might have missed something, looking forward to input.
Best regards
Per