1 个不稳定版本
0.1.0 | 2024年2月21日 |
---|
#20 in #hard
2KB
SCARS
SCARS是一个为硬实时应用设计的实时操作系统(RTOS),受Ada Ravenscar配置文件的启发。它是一个传统风格的RTOS,具有固定优先级抢占式调度,不是异步执行器。它仍在开发中,不建议用于生产系统。目前仅实现了RISC-V(无FPU)和基于pthread的模拟器。计划支持ARM和FPU。
-
它实现了即时优先级上限协议,并为任务和中断提供了一个统一的优先级模型,允许开发者在任务和中断中使用相同的API。互斥量保护的数据可以从中断处理程序中访问。
-
所有任务和中断处理程序都是静态分配的,但在运行时启动,允许任务和中断处理程序捕获它们启动时的环境变量。不需要全局静态变量。
-
所有依赖于任务数量或顺序的调度操作都在允许中断的抢占锁内执行,以最大限度地减少最大中断延迟。中断延迟与调度和任务数量无关。
-
低于优先级上限的任何基于优先级的同步原语都不会阻止高优先级任务或中断的执行。
任务
入口任务使用entry
属性定义为一个函数。只能有一个入口任务。任务永远不会退出。
#[scars::entry(name = "main", priority = 1, stack_size = 2048)]
fn main() {
loop {}
}
其他任务使用make_task!
宏创建,该宏静态分配任务状态,包括任务堆栈。
const TASK_PRIO: TaskPriority = 2;
const TASK_STACK_SIZE: usize = 1024;
const CHANNEL_CEILING: AnyPriority = Priority::any_task_priority(TASK_PRIO);
const CHANNEL_CAPACITY: usize = 1;
#[scars::entry(name = "main", priority = 1, stack_size = 2048)]
fn main() {
let task = make_task!("task", TASK_PRIO, TASK_STACK_SIZE);
let (sender, receiver) = make_channel!(u32, CHANNEL_CAPACITY, CHANNEL_CEILING);
task.start(move || {
// Task closure captures `sender`
let mut counter: u32 = 0;
loop {
sender.send(counter);
counter += 1;
}
});
loop {
let value = receiver.recv();
println!("Received {:?}", value);
}
}
空闲任务
内核创建一个内部空闲任务,在没有其他任务准备就绪时运行。空闲任务是特殊的,因为它不遵循优先级上限协议,即使在更高优先级任务持有锁的情况下也可以运行。
要在空闲任务中运行自定义代码,用户可以使用idle_task_hook
属性定义空闲任务钩子
#[scars::idle_task_hook]
fn idle_task_hook() {
// do something when idle
}
钩子函数可以使用InterruptLock
和PreemptLock;然而,由于免除优先级上限协议,它可能无法获取
CeilingLock
。从空闲任务尝试获取CeilingLock
将导致运行时错误。
中断处理程序
中断处理程序类似于任务,使用make_interrupt_handler!
宏创建。
use scars::pac;
const INTERRUPT_PRIO: InterruptPriority = 2;
const CHANNEL_CEILING: AnyPriority = Priority::any_interrupt_priority(INTERRUPT_PRIO);
const CHANNEL_CAPACITY: usize = 10;
#[scars::entry(name = "main", priority = 1, stack_size = 2048)]
fn main() {
let uart_handler = make_interrupt_handler!(pac::Interrupt::UART1, INTERRUPT_PRIO);
let (sender, receiver) = make_channel!(u32, CHANNEL_CAPACITY, CHANNEL_CEILING);
let mut counter: u32 = 0;
uart_handler.attach(move || {
// Interrupt handler closure captures `sender` and `counter`
counter += 1;
// Must use non-blocking `try_send` in interrupt
let _ = sender.try_send(counter);
});
uart_handler.enable_interrupt();
loop {
let value = receiver.recv();
println!("Handled {:?} interrupts", value);
}
}
即时优先级上限协议
任务和中断处理程序具有固定的基本优先级,所有基于优先级的锁定原语都有优先级上限,这是可以获取该锁的任何任务或中断的最大优先级。获取基于优先级的锁将立即将任务或中断的当前优先级提升到该锁的上限优先级,防止其他任务或中断获取该锁。基于优先级的锁定在调度器的就绪队列中实现互斥。
如果锁的上限位于中断优先级,则中断将阻塞至该优先级。允许更高优先级的中断执行,但由于它们位于上限之上,因此它们永远不会被允许获取锁。这种做法的好处是,每当中断处理程序执行时,它可以访问由优先级锁保护的数据;如果任务或较低优先级的中断处理程序持有该锁,则不允许中断处理程序首先执行。缺点是,所有锁定原语,如互斥锁和条件变量,都必须由开发者指定上限优先级。
通过尝试从优先级高于锁上限优先级的任务或中断处理程序获取锁,违反优先级上限协议,将导致运行时错误。
同步原语
- 锁:
InterruptLock
,PreemptLock
和CeilingLock
互斥锁
条件变量
通道
- 其他待定
模拟器
scars-khal-sim
包实现了在主机上的基于 pthreads 的模拟器。中断模拟尚未实现。
运行测试
khal-e310x
.cargo/config.toml 中的配置是为 QEMU。
$ cargo test --release --package=scars --features="khal-e310x" --target=riscv32imac-unknown-none-elf
khal-sim
$ cargo test --release --package=scars --features="khal-sim" --target=x86_64-unknown-linux-gnu