文章目录
- 1. TockOS介绍
- 详细总结
- 2. TockOS开源项目的目录结构
- 3. 胶囊(Capsules)
- 胶囊的本质
- 胶囊的特点
- 胶囊的应用场景
- 4. 胶囊的实现
- 模块化设计
- 安全隔离
- 事件驱动
- 可复用性
1. TockOS介绍
Tock 是一款面向 Cortex-M 和 RISC-V 微控制器的安全嵌入式操作系统,依赖硬件内存保护单元(MPU)保障安全。其内核与扩展(胶囊)用 Rust 编写,能运行多种语言的多个独立不可信进程,进程数量受微控制器闪存和内存限制,默认采用抢占式循环调度算法。Tock 采用微内核架构,内核提供 command、subscribe、allow、yield 四个主要系统调用,通过 MPU 保护自身和其他进程免受恶意进程代码侵害,同时借助 Rust 的类型安全特性确保内核和胶囊代码的安全性。
详细总结
- Tock概述:Tock是用于Cortex-M和RISC-V微控制器的安全嵌入式操作系统,要求硬件配备内存保护单元(MPU),内核和胶囊使用Rust编写。
- 进程相关
- 编写语言与数量限制:支持用任意语言编写多个独立的不可信进程,进程数量受微控制器闪存和随机存取存储器(RAM)的限制。
- 调度算法:默认采用抢占式循环调度算法,也可配置使用其他调度算法。
- 系统架构
- 代码分类:代码分为核心内核、胶囊和进程三类。核心内核因需操作特定内存地址外设等功能,可使用“unsafe” Rust代码;胶囊不能使用unsafe特性,以保证代码安全。进程可由任意语言编写。
- 系统调用:内核提供四个主要系统调用,具体如下:
系统调用 | 功能 | 特点 | 参数 |
---|---|---|---|
command | 进程向内核发起调用 | 非阻塞,长时间操作会立即返回并在完成时发出回调 | 驱动ID |
subscribe | 在进程中注册用于接收内核上调用的回调 | 非阻塞 | 驱动ID |
allow | 允许内核访问进程内存 | 非阻塞 | 驱动ID |
yield | 暂停进程直到回调被调用 | 阻塞 | 无 |
- 安全机制:借助硬件MPU,若进程试图访问非法内存,会触发异常,内核捕获异常并终止该进程,以此保护内核和其他进程。同时,Rust的类型安全特性防止内存错误使用,保障内核和胶囊代码安全。
2. TockOS开源项目的目录结构
TockOS的开源项目的目录结构如下图:
3. 胶囊(Capsules)
Tock 是一个为物联网(IoT)设备设计的开源、多任务、安全的嵌入式操作系统。在 Tock OS 里,“胶囊(Capsules)”是其核心抽象概念之一,下面为你介绍其本质。
胶囊的本质
胶囊本质上是一种模块化的软件组件,是Tock OS用于实现设备驱动和系统服务的关键机制。它以面向对象的方式来封装设备的功能和状态,能把不同的硬件设备和系统服务进行抽象,从而让它们能在 Tock 内核中以独立、安全且可复用的形式存在。
胶囊的特点
- 模块化设计:胶囊具备模块化特性,每个胶囊都负责特定的功能,像传感器驱动、通信协议栈或者文件系统服务等。如此一来,开发者可以按需添加或者移除胶囊,以此实现系统功能的定制。
- 安全隔离:Tock OS 借助内存保护和能力机制保证各个胶囊间的安全隔离。每个胶囊只能访问其被授权的资源,这样就能防止恶意或者有缺陷的胶囊对系统其他部分造成影响。
- 事件驱动:胶囊采用事件驱动的编程模型。胶囊会对来自硬件或者其他胶囊的事件做出响应,然后执行相应的操作。这种模型有助于降低系统的复杂性,提高系统的响应能力。
- 可复用性:胶囊是可复用的软件组件。开发者可以在不同的项目中复用已有的胶囊,或者基于现有的胶囊开发新的功能。
胶囊的应用场景
- 设备驱动:为各类硬件设备(如传感器、执行器等)实现驱动程序。
- 系统服务:实现诸如网络协议栈、文件系统、电源管理等系统服务。
- 应用程序支持:为应用程序提供必要的接口和服务,确保应用程序能够安全、高效地运行。
4. 胶囊的实现
以下将结合 Tock OS 源代码来解释胶囊的实现,聊聊胶囊的各个特点以及对应的源代码。在 Tock OS 里,胶囊通常是 Rust 语言编写的结构体和相关的特征(trait)实现。
模块化设计
模块化设计体现为每个胶囊负责特定功能,且独立于其他胶囊。以 virtual_alarm
胶囊为例,它实现了一个虚拟闹钟服务。
// 定义一个虚拟闹钟的结构体
pub struct VirtualMuxAlarm<'a, A: Alarm> {alarm: &'a A,// 其他状态字段next_alarm: Option<Ticks>,callbacks: VecDeque<(Ticks, Callback)>,
}// 为 VirtualMuxAlarm 实现相关的方法和特征
impl<'a, A: Alarm> VirtualMuxAlarm<'a, A> {// 初始化虚拟闹钟pub fn new(alarm: &'a A) -> VirtualMuxAlarm<'a, A> {VirtualMuxAlarm {alarm: alarm,next_alarm: None,callbacks: VecDeque::new(),}}// 设置闹钟pub fn set_alarm(&mut self, when: Ticks, callback: Callback) {// 实现设置闹钟的逻辑// ...}
}
在上述代码中,VirtualMuxAlarm
结构体封装了虚拟闹钟的功能和状态。它依赖于一个实现了 Alarm
特征的底层闹钟设备,但本身是一个独立的模块,可被其他模块复用。
安全隔离
Tock OS 通过内存保护和能力机制确保胶囊间的安全隔离。在 Rust 中,这主要通过所有权和借用规则来实现。例如,在 virtual_alarm
胶囊中,VirtualMuxAlarm
结构体持有对底层闹钟设备的不可变引用:
pub struct VirtualMuxAlarm<'a, A: Alarm> {alarm: &'a A,// ...
}
这里的生命周期参数 'a
确保了 VirtualMuxAlarm
不会持有底层闹钟设备的引用超过其生命周期,避免了悬空引用和内存安全问题。同时,胶囊只能访问其被授权的资源,通过 Rust 的私有性规则(pub
和非 pub
成员)来限制对内部状态的访问。
事件驱动
胶囊采用事件驱动的编程模型。以 button
胶囊为例,它会对按钮按下和释放事件做出响应:
// 定义按钮事件的回调特征
pub trait ButtonClient {fn pressed(&self, btn_num: usize);fn released(&self, btn_num: usize);
}// 定义按钮胶囊结构体
pub struct Button<'a, C: ButtonClient> {client: &'a C,// 其他状态字段btn_state: [bool; NUM_BUTTONS],
}// 实现按钮胶囊的方法
impl<'a, C: ButtonClient> Button<'a, C> {// 处理按钮事件pub fn handle_interrupt(&mut self) {for i in 0..NUM_BUTTONS {let new_state = self.read_button_state(i);if new_state != self.btn_state[i] {if new_state {self.client.pressed(i);} else {self.client.released(i);}self.btn_state[i] = new_state;}}}// 读取按钮状态fn read_button_state(&self, btn_num: usize) -> bool {// 实现读取按钮状态的逻辑// ...}
}
在这个例子中,Button
胶囊会在检测到按钮状态变化时调用 ButtonClient
特征的 pressed
或 released
方法,从而实现事件驱动的编程模型。
可复用性
胶囊的可复用性体现在多个方面。例如,virtual_alarm
胶囊可以在不同的项目中复用,只要提供一个实现了 Alarm
特征的底层闹钟设备即可:
// 在某个项目中使用 VirtualMuxAlarm
let hardware_alarm = get_hardware_alarm();
let virtual_alarm = VirtualMuxAlarm::new(&hardware_alarm);// 设置闹钟
virtual_alarm.set_alarm(Ticks::from_ms(1000), Callback::new(...));
通过这种方式,开发者可以在不同的项目中复用 VirtualMuxAlarm
胶囊,而无需重新实现闹钟功能。