Skip to content

Goodby Exclusive, Welcome Symmetrical Lock and [mut/&resource] optimization. #17

@perlindgren

Description

@perlindgren

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

  1. Introducing another task in the application may break code. gpiob would need to be changed API if allowing gpioc access to shared.
  2. If we want gpioa to pass the resource access to another (external) function (like a HAL), we would need to wrap shared by Exclusive (which gives a lock API to inner).
  3. Context cannot have 'static lifetime if allowed to have &mut T in resources. 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 for async/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 the codegen).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions