Project Status

The Kernel is still under development and a first release (including documentation) is planned for end of June 2021.

The Kernel currently features:

  • A priority based preemtive scheduler with Round Robin
  • Synchronization primitives (Mutex, Semaphore)

Here are some code examples showing how the kernel could be used.

Creating a task

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fn main() -> ! {
    let board = StNucleoF446::new();
    sched::init();
    
    let mut led = board.shield.led_7;
    Task::new()
        .priority(Priority(1))
        .static_stack(kernel::alloc_static_stack!(512))
        .spawn(move || {
            loop {
                led.toggle().ok();
                kernel::sleep(100);
            }
        });

    shed::start();
}

As familiar the work of a task is done in a loop (10-13), but in contrast to C based RTOS the task entry is written as closure (similar to C++ lambda functions). This allows for parameters to be passed to a task without type conversion.

Before the task is spawend its priority (7) and stack (8) can be set. Currently only statically allocated stacks are supported as no heap management has been implemented yet.

A running task can delay with the sleep function (12).

move (9) instructs the compiler to move resources captured by the closure out of the main function. Thus, moved resources cannot be used in other tasks. To share resources between tasks a mutex can be used.

Mutex

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#[link_section = ".shared"]
static MUTEX: Mutex<u32> = Mutex::new(42); // resource is wrapped in mutex

fn main() -> ! {
    /*...*/
    
    let mut led = board.shield.led_7;
    Task::new()
        .static_stack(kernel::alloc_static_stack!(512))
        .spawn(move || {
            loop {
                match MUTEX.lock(1000) { // wait for mutex with timeout
                    Ok(mut value) => *value += 1,
                    Err(_) => (),
                } 

                led.toggle().ok(); // capture resource from `main()`
                kernel::sleep(100);
            }
        });

    Task::new()
        .static_stack(kernel::alloc_static_stack!(512))
        .spawn(move || {
            loop {
                // try to lock the mutex (no timeout)
                match MUTEX.try_lock() { 
                    Ok(mut value) => {
                        *value -= 1;
                        kernel::sleep(100); // block mutex for 100ms
                    }, // mutex is unlocked automatically
                    Err(_) => (),
                }
                kernel::sleep(200);
            }
        });

    shed::start();
}

A mutex has to be declared in a shared memory space (1-2) in order for multiple tasks to have access.

A task can either wait until the mutex is free (12) or try to lock the mutex (27) in a non-blocking manner. In comparison to C the data/resource protected by the mutex is wrapped in the mutex and unlocking is done automatically.

Semaphore

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[link_section = ".shared"]
static SEMAPHORE: Semaphore = Semaphore::new(4); 
// counting semaphore with 4 permits

fn main() -> ! {
    /*...*/
    
    Task::new()
        .static_stack(kernel::alloc_static_stack!(512))
        .spawn(move || {
            loop {
                // wait for permit with timeout
                match SEMAPHORE.acquire(100) {
                    Ok(_) => do_something(),
                    Err(_) => (),
                } 

                led.toggle().ok(); // capture resource from `main()`
                kernel::sleep(100);
            }
        });

    /*...*/
    shed::start();
}

The usage of the semaphore is similar to the mutex. It is also placed in shared memory but no data is wrapped in the semaphore. Instead the number of permits is set.

Acquiring a permit is identical to a mutex (13).