-
Notifications
You must be signed in to change notification settings - Fork 3
Programming Guide
Tasks in AltOS Rust are created through the new_task system call. The signature for the system call is
fn new_task(code: fn(&mut Args),
args: Args,
stack_depth: usize,
priority: Priority,
name: &'static str)
-> TaskHandleCalling this function will register the new task with the scheduler and place it in the appropriate priority queue and return a handle to the task. On startup, the scheduler is not running, once you have finished creating all the tasks you need for the application call the start_scheduler function.
An example task that takes no arguments.
use altos_core::{syscall, start_scheduler, args, Priority};
let handle = syscall::new_task(test_task, args::Args::empty(), 1024, Priority::Normal, "test_task");
start_scheduler();
fn test_task(_args: &mut args::Args) {
loop {
// Do some stuff...
}
}If a task needs some kind of arguments, they can be passed into the task through the Args struct. In order to create an Args struct you use an ArgsBuilder object to specify each argument. From within the task you use the Args object to access each argument.
An example task that supervises another task.
use altos_core::{syscall, start_scheduler, args, TaskHandle, Priority};
let handle = syscall::new_task(test_task, args::Args::empty(), 1024, Priority::Normal, "test_task");
let builder = args::ArgsBuilder::with_capacity(1);
builder.add_box(handle);
syscall::new_task(supervisor, builder.finalize(), 1024, Priority::Normal, "supervisor");
start_scheduler();
fn test_task(_args: &mut args::Args) {
loop {
}
}
fn supervisor(args: &mut args::Args) {
let handle = unsafe { args.pop_box::<TaskHandle>() };
loop {
match handle.state() {
Ok(state) => {/* Do something now that we know the state of the other task */},
Err(_) => {/* The other task has been destroyed */},
}
}
}The TaskHandle allows you to query information about a given task as well as mark it for destruction. All methods on a TaskHandle return the HandleResult type, which is an error only if the task is invalid (i.e. has been destroyed), otherwise it contains the data being queried. The methods available on a TaskHandle are:
handle.priority(); // Return the priority of the task, or `Err`
handle.state(); // Return the state of the task, or `Err`
handle.tid(); // Return the task ID of the task, or `Err`
handle.name(); // Return the name of the task, or `Err`
handle.stack_size(); // Return the stack size of the task, or `Err`
handle.is_valid(); // Return true if the task is valid, false otherwise.
// Calling this does not guarantee that a subsequent
// call to one of the other methods will succeed
handle.destroy(); // Mark the task for destruction, returns true if the
// task was valid, false if it was already invalid. Any
// subsequent calls to the other methods will always failIf you have multiple tasks that need access to some shared piece of data you can control access to that data with a Mutex. In order to access the data you must first acquire the lock on the mutex, only then will you be able to read or modify the data.
Two tasks trying to access the same data.
use altos_core::{syscall, start_scheduler, args, TaskHandle, Priority};
use altos_core::sync::Mutex;
static DATA: Mutex<u32> = Mutex::new(0);
syscall::new_task(task_1, args::Args::empty(), 1024, Priority::Normal, "task_1");
syscall::new_task(task_2, args::Args::empty(), 1024, Priority::Normal, "task_2");
start_scheduler();
fn task_1(_args: &mut args::Args) {
loop {
// If the lock is held by task_2, this thread will sleep
let data = DATA.lock();
*data += 1;
// The lock is implicitly released at the end of this block
}
}
fn task_2(_args: &mut args::Args) {
loop {
// If the lock is held by task_1, this thread will sleep
let data = DATA.lock();
*data -= 1;
// The lock is implicitly released at the end of this block
}
}Sometimes one task relies on another in order to carry out some work. For this we can use the CondVar type. This type lets us check if a condition is true, and if not wait for some signal from another task to resume.
use altos_core::{syscall, start_scheduler, args, TaskHandle, Priority};
use altos_core::sync::{CondVar, Mutex};
static DATA: Mutex<Option<u32>> = Mutex::new(None);
static CV: CondVar = CondVar::new();
syscall::new_task(consumer, args::Args::empty(), 1024, Priority::Normal, "producer");
syscall::new_task(producer, args::Args::empty(), 1024, Priority::Normal, "consumer");
start_scheduler();
fn consumer(_args: &mut args::Args) {
let mut sum = 0;
let data = DATA.lock();
loop {
if let Some(inner) = data.take() {
sum += inner;
}
else {
// Tell the producer that we're ready for more data
CV.notify_all();
CV.wait(data);
// We reacquire the lock after we wake up
}
}
}
fn producer(_args: &mut args::Args) {
let mut count = 0;
let data = DATA.lock();
loop {
if data.is_none() {
*data = Some(count);
count += 1;
}
else {
// Tell the consumer that data is ready
CV.notify_all();
CV.wait(data);
// We reacquire the lock after waking back up
}
}
}