#async #operating-system #micro-controller #os #resources #embedded-os

no-std lilos

基于Futures和async的微型嵌入式操作系统

19个版本 (4个稳定版)

1.2.0 2024年5月5日
1.1.0 2024年4月29日
1.0.0-pre.22024年3月6日
0.3.6 2023年7月11日
0.1.2 2021年4月29日

#106 in 嵌入式开发


4 个crate中使用

MPL-2.0 许可证

195KB
2K SLoC

lilos

这是一个专为支持在微控制器上以Rust的async风格进行编程而编写的微小操作系统。它大约占用2 kiB的Flash空间,并使用约40字节RAM(在你添加任务之前)。在这个空间中,你将获得一个完整的async运行时环境,具有多个任务,通过joinselect支持复杂的并发,以及许多方便且简单的API。(如果你想看看lilos程序的样子,请查看examples目录,或阅读入门指南。)

lilos自2019年以来已在实际的嵌入式系统中部署,并持续运行。我已围绕它构建了大约十几个不同复杂性的系统,在六种不同的微控制器上。它运行得相当不错!也许你会发现它也很有用。

请参阅仓库中的示例代码和入门说明,或查看顶级lilos模块的Rustdoc以获取API详情和模块描述。


lib.rs:

一个基于Rust Future的简单但强大的async RTOS。

这为在ARM Cortex-M微处理器上运行异步Rust代码提供了一个轻量级的操作环境,以及一些有用的工具。

lilos被故意设计为紧凑,以避免使用proc宏,以高度可移植到不同的微控制器,并以尽可能静态可预测的方式使用,不进行动态资源分配。

这些是操作系统的API文档。如果你想看一个高级介绍和示例,请查看入门指南

关于操作系统

lilos的设计理念是基于一组始终运行的并发任务。要使用操作系统,您的应用程序启动例程需要调用exec::run_tasks,并给它一个您定义的任务数组;run_tasks永远不会返回。

操作系统提供的是协作式多任务处理:虽然任务可以并发执行,但它们不是抢占式的,也不是传统意义上的“线程”。任务甚至没有自己的堆栈——只要它们让出CPU,就会完全返回。

如果没有Futureasync,这将使得编程变得极其令人沮丧。

每个任务协程必须是一个可以轮询但永远不会完成的Future(因为,记住,任务会永远运行)。操作系统提供一个执行器来管理一组Future的轮询。

Rust的async关键字提供了一种方便的方式,让编译器将一个普通函数重写为协程风格的Future。这意味着在这个操作系统上编写协程看起来非常像使用线程进行编程。

有关详细讨论和一些菜谱示例,请参阅入门指南存储库中的examples目录

特性标志

lilos目前公开以下Cargo特性,用于启用/禁用系统的某些部分

  • systick(默认开启)。启用依赖ARM M-profile SysTick定时器进行可移植的时间管理。禁用此功能将使执行器更小,但会丢失所有time API。在SysTick定时器在睡眠期间停止的平台(如Nordic nRF52)上,您可能希望禁用此功能并使用不同的时间管理机制。

  • mutex(默认开启)。启用对mutex模块的访问,用于阻塞对共享数据的访问。如果实际上没有使用mutex,启用此功能没有成本。

  • spsc(默认开启)。启用对spsc模块的访问,用于单生产者单消费者跨任务队列。如果实际上没有使用spsc,启用此功能没有成本。

组合和动态行为

一组固定任务的概念可能看起来很有限,但实际上它比您想象的要灵活。因为Future可以组合,所以操作系统中的固定任务集可以驱动一个动态的程序Future集。

例如,一个任务可以使用来自futurescrate的宏(如select_biased!join!)创建多个并发例程。

并发和中断

操作系统支持通过Notify机制使用中断处理程序唤醒任务,但大多数操作系统功能在中断上下文中不可用。

默认情况下,当任务代码运行时,中断会被屏蔽,因此任务可以确信只有在它们await时才会被抢占。

每次通过任务轮询循环,操作系统都会取消中断屏蔽,以便运行任何挂起的中断。因为Cortex-M在屏蔽中断时收集挂起的中断,所以我们不会错过任何事件。

每当空闲的处理器从睡眠中唤醒时,也会取消中断屏蔽,以处理唤醒它的事件。

如果您的应用程序需要更紧的响应中断时间,您可以在启动时配置操作系统以允许各种抢占,包括只允许部分中断抢占。有关更多详细信息和一些定制选项,请参阅exec模块。

取消

在此操作系统中的协程任务实际上是Future,这意味着它们可以被丢弃。通常在它们解决后(通常在调用代码中的await关键字后)会丢弃Future,但也可以在挂起时丢弃Future。这可以显式地发生(通过调用drop),或者作为其他操作的副作用;例如,宏select_biased!等待一个未来解决,然后丢弃其他所有未来,无论它们是否完成。

这意味着考虑任何特定任务的取消含义是有用的,并确保其结果符合您的意图。有关更多信息,请参阅入门指南和关于取消的技术说明。

lilos试图通过提供具有合理取消行为的API来使您更容易处理程序中的取消,所有核心API都旨在实现严格的取消安全性,其中丢弃未来并重试产生它的操作与不丢弃未来在可见副作用方面等效。(显然,做更多的工作将需要更多的CPU周期;这不是我们所说的副作用。)

如果某些代码有用,但不能达到严格的取消安全性,则应将其放入单独的crate中。例如,lilos-handoff crate 曾经是核心操作系统的一部分,但由于它只能达到较弱的取消安全性概念,因此被移除。

任何与此原则不符的偏差都被视为错误,如果您发现它们,应报告它们!

依赖关系

~2MB
~42K SLoC