Processes

Processes in Drone OS are special kind of fibers, that can be suspended with a special blocking call. They use dedicated dynamically allocated stacks. On Cortex-M platform, Drone implements processes using SVC assembly instruction and SVCall exception. So before using processes, a Drone supervisor should be added to the project.

Supervisor

Create a new file at src/sv.rs with the following content:


#![allow(unused)]
fn main() {
//! The supervisor.

use drone_cortexm::{
    sv,
    sv::{SwitchBackService, SwitchContextService},
};

sv::pool! {
    /// Pool of services.
    pool => SERVICES;

    /// Supervisor type.
    supervisor => pub Sv;

    // Attached services.
    services => {
        SwitchContextService;
        SwitchBackService;
    }
}
}

And register the newly created module in the src/lib.rs:


#![allow(unused)]
fn main() {
pub mod sv;
}

Update thr::nvic! macro inside src/thr.rs as follows:


#![allow(unused)]
fn main() {
use crate::sv::Sv;

thr::nvic! {
    supervisor => Sv; // <-- register the supervisor type

    // ... other configuration is skipped ...

    threads => {
        exceptions => {
            /// All classes of faults.
            pub hard_fault;
            // Define an external function handler for the SV_CALL exception.
            naked(sv::sv_handler::<Sv>) sv_call;
        };
    };
}
}

Using processes

First, let's recall the generator fiber example from the previous chapter:


#![allow(unused)]
fn main() {
use core::pin::Pin;
use drone_cortexm::{
    fib,
    fib::{Fiber, FiberState},
};

let mut fiber = fib::new(|| {
    let mut state = 0;
    while state < 3 {
        state += 1;
        yield state;
    }
    state
});
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Yielded(1));
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Yielded(2));
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Yielded(3));
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Complete(3));
}

This fiber can be rewritten using Drone process as follows:


#![allow(unused)]
fn main() {
use crate::sv::Sv;
use core::pin::Pin;
use drone_cortexm::{
    fib,
    fib::{Fiber, FiberState},
};

let mut fiber = fib::new_proc::<Sv, _, _, _, _>(128, |_, yielder| {
    let mut state = 0;
    while state < 3 {
        state += 1;
        yielder.proc_yield(state);
    }
    state
});
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Yielded(1));
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Yielded(2));
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Yielded(3));
assert_eq!(Pin::new(&mut fiber).resume(()), FiberState::Complete(3));
}

The difference is that the code inside the closure argument is fully synchronous. The proc_yield call is translated to the SVC assembly instruction. This instruction immediately switches the execution context back to the caller. When the resume method of the process is called, it continues from the last yield point, just like a generator.

The fib::new_proc function takes a stack size as the first argument. The stack will be immediately allocated from the heap. To make this function safe, the processor's MPU used to protect the stack from a possible overflow. On processors without MPU, like STM32F103, this function will panic. However it is still possible to use processes on such systems, though without any guarantees about stack overflows. You can use new_proc_unchecked function, which is marked unsafe.

Unlike generators, a process can take input data. And unlike yield keyword, the proc_yield function not necessarily returns (). Here is an example of such process:


#![allow(unused)]
fn main() {
let mut fiber = fib::new_proc::<Sv, _, _, _, _>(128, |input, yielder| {
    let mut state = input;
    while state < 4 {
        state += yielder.proc_yield(state);
    }
    state
});
assert_eq!(Pin::new(&mut fiber).resume(1), FiberState::Yielded(1));
assert_eq!(Pin::new(&mut fiber).resume(2), FiberState::Yielded(3));
assert_eq!(Pin::new(&mut fiber).resume(3), FiberState::Complete(6));
}