嵌入式操作系统的系统资源分离与任务调度
1. 系统资源分离与系统调用
在系统资源分离方面,一些操作限制了当前任务直接访问资源的可能性。为了维持系统原有功能,系统需要导出新的安全 API 供任务请求系统操作。
以下是相关代码示例:
DMB();
mpu_set_region(3, (uint32_t)start, attr);
MPU_CTRL = 1;
系统调用方面,最新的操作系统不再允许任务直接控制系统资源,如输入输出外设,甚至不允许任务主动阻塞。操作系统通过 SVCall 异常的系统调用机制导出 API,由
isr_svc
服务例程处理,任务可通过
svc
指令随时触发。
定义了一个快捷宏
SVC()
用于切换到处理模式:
#define SVC() asm volatile ("svc 0")
封装在 C 函数中以传递参数:
static int syscall(int arg0)
{
SVC();
}
系统调用列表如下:
| 系统调用名称 | 标识编号 |
| — | — |
| SYS_SCHEDULE | 0 |
| SYS_BUTTON_READ | 1 |
| SYS_BLUELED_ON | 2 |
| SYS_BLUELED_OFF | 3 |
| SYS_BLUELED_TOGGLE | 4 |
| SYS_REDLED_ON | 5 |
| SYS_REDLED_OFF | 6 |
| SYS_REDLED_TOGGLE | 7 |
| SYS_GREENLED_ON | 8 |
| SYS_GREENLED_OFF | 9 |
| SYS_GREENLED_TOGGLE | 10 |
isr_svc
函数用于处理这些系统调用:
void __attribute__((naked)) isr_svc(int arg)
{
store_user_context();
asm volatile("mrs %0, psp" : "=r"(t_cur->sp));
if (t_cur->state == TASK_RUNNING) {
t_cur->state = TASK_READY;
}
switch(arg) {
case SYS_BUTTON_READ: /* cmd to read button value */
button_start_read();
break;
case SYS_SCHEDULE: /* cmd to schedule the next task */
t_cur = tasklist_next_ready(t_cur);
t_cur->state = TASK_RUNNING;
break;
case SYS_BLUELED_ON: /* cmd to turn on blue LED */
blue_led_on();
break;
/* case ... (more LED related cmds follow) */
}
if (t_cur->id == 0) {
asm volatile("msr msp, %0" ::"r"(t_cur->sp));
restore_kernel_context();
asm volatile("mov lr, %0" ::"r"(0xFFFFFFF9));
asm volatile("msr CONTROL, %0" ::"r"(0x00));
} else {
asm volatile("msr psp, %0" ::"r"(t_cur->sp));
restore_user_context();
mpu_task_stack_permit(((uint8_t *)((&stack_space))
+(t_cur->id << 10)));
asm volatile("mov lr, %0" ::"r"(0xFFFFFFFD));
asm volatile("msr CONTROL, %0" ::"r"(0x01));
}
asm volatile("bx lr");
}
任务代码示例:
void task_test0(void *arg)
{
while(1) {
syscall(SYS_BLUELED_ON);
mutex_lock(&m);
sleep_ms(500);
syscall(SYS_BLUELED_OFF);
mutex_unlock(&m);
sleep_ms(1000);
}
}
void task_test1(void *arg)
{
syscall(SYS_REDLED_ON);
while(1) {
sleep_ms(50);
mutex_lock(&m);
syscall(SYS_REDLED_TOGGLE);
mutex_unlock(&m);
}
}
void task_test2(void *arg)
{
uint32_t toggle_time = 0;
syscall(SYS_GREENLED_OFF);
while(1) {
button_read();
if ((jiffies - toggle_time) > 120) {
syscall(SYS_GREENLED_TOGGLE);
toggle_time = jiffies;
}
}
}
2. 嵌入式操作系统选择
在选择嵌入式操作系统时,需要考虑诸多因素,如硬件特性、与第三方库的集成、与外设和接口的交互功能,以及系统设计所覆盖的用例范围。
操作系统通常除了调度器和内存管理外,还包含一系列集成库、模块和工具,可能涵盖以下方面:
- 特定平台的硬件抽象层
- 常见外设的设备驱动
- 用于连接的 TCP/IP 栈集成
- 文件系统和文件抽象
- 集成电源管理系统
根据调度器中线程模型的实现,有些系统使用编译时配置的固定任务数量,而有些则支持运行时创建和终止线程。不过,动态任务创建和终止在嵌入式系统中需求较少。
复杂系统可能会因系统异常代码中的额外逻辑引入开销,不太适合关键实时操作。因此,大多数成功的实时操作系统(RTOS)保持简单架构,采用扁平内存模式运行多线程,以降低延迟并满足实时要求。
3. 流行的开源嵌入式操作系统 - FreeRTOS
FreeRTOS 是嵌入式设备中最流行的开源操作系统之一,具有近 20 年的活跃开发历史,可移植性极高,支持众多嵌入式平台和 CPU 架构。
它专注于线程的实时调度和堆内存管理,代码简洁、接口简单。第三方库和硬件制造商的示例代码常将其集成到软件套件中。
3.1 任务创建与调度
FreeRTOS 的调度器是抢占式的,具有固定优先级和通过共享互斥锁实现的优先级继承。任务优先级和栈空间大小在创建线程时确定。
使用
xTaskCreate
函数创建新任务:
xTaskCreate(task_entry_fn, "TaskName", task_stack_size,
( void * ) custom_params, priority, task_handle);
启动调度器:
vTaskStartScheduler();
3.2 堆内存管理
FreeRTOS 提供五种堆内存管理模式:
-
Heap 1
:仅允许一次性静态堆分配,无法释放内存。
-
Heap 2
:允许释放内存,但不重新组合已释放的块,适用于堆分配数量有限的情况。
-
Heap 3
:封装第三方库的
malloc/free
实现,确保线程安全。
-
Heap 4
:支持内存合并,减少堆碎片化,提高长期内存使用率。
-
Heap 5
:与 Heap 4 机制相同,但允许定义多个非连续内存区域作为同一堆空间。
选择特定堆模型只需包含相应的源文件(如
heap_1.c
等)。堆内存管理器暴露的重要函数有
pvPortMalloc
和
pvPortFree
。
3.3 其他特性
支持 MPU 和线程模式,线程可在受限模式下运行,仅能访问分配给自己的内存。低功耗管理默认限于睡眠模式,但允许重新定义调度器回调函数以实现自定义低功耗模式。
近期版本的 FreeRTOS 包含第三方代码的特定发行版,用于构建物联网系统的安全连接平台,还附带专门设计的 TCP/IP 栈和 wolfSSL 库支持安全套接字通信。
以下是 FreeRTOS 相关操作的流程图:
graph TD;
A[开始] --> B[创建任务];
B --> C[启动调度器];
C --> D[任务运行];
D --> E{是否需要内存分配};
E -- 是 --> F[选择堆模型];
F --> G[进行内存分配];
E -- 否 --> D;
D --> H{是否结束};
H -- 是 --> I[结束];
H -- 否 --> D;
嵌入式操作系统的系统资源分离与任务调度
4. 流行的开源嵌入式操作系统 - RIOT OS
RIOT OS 主要构建在受限微控制器上,适用于低功耗嵌入式系统,如物联网项目和长期运行且维护成本低的场景。这类系统通常使用电池供电或能量收集技术,通过无线技术与远程服务进行间歇性连接。
4.1 系统特点
RIOT OS 与 FreeRTOS 的简约理念不同,它提供了广泛的库和设备支持代码,包括网络栈和无线驱动通信,对物联网应用友好。系统核心具有高度可扩展性,部分组件可在编译时排除。
从 API 角度看,RIOT OS 尽量模仿 POSIX 接口,方便不同背景的程序员开发嵌入式应用。不过,系统采用扁平模型,未在系统层面实现权限分离,用户空间应用仍需直接引用系统内存来访问资源。
为增强安全性,可使用 MPU 检测单线程的栈溢出,方法是在栈底部设置一个小的只读区域,若线程试图越界写入,将触发异常。
4.2 网络通信与低功耗管理
RIOT OS 实现了多个通信栈模块,如 GNRC 极简 IP 栈,专为 802.15.4 网络特性定制,仅支持 IPv6,并提供套接字实现以编写轻量级物联网应用。此外,还包含 lwIP 兼容性层和 WolfSSL 模块,可提供更完整的 TCP/IP 实现和安全套接字通信。
低功耗管理通过电源管理模块集成到系统中,该模块提供了管理特定平台低功耗模式(如 Cortex - M 平台的停止和待机模式)的抽象接口。应用程序可在运行时激活低功耗模式,并利用实时时钟、看门狗定时器或其他外部信号恢复正常运行。
4.3 任务调度与创建
RIOT OS 的调度器是无滴答且基于协作的。任务可通过调用
task_yield
函数或任何阻塞函数(如访问内核功能或硬件外设的函数)显式挂起自己。系统仅在接收到硬件中断时强制中断任务。
使用
thread_create
函数创建任务:
thread_create(task_stack_mem, task_stack_size, priority,
flags, task_entry_fn, (void*)custom_args, "TaskName");
与 FreeRTOS 不同,RIOT OS 要求调用者手动分配任务的栈空间,这增加了调用代码的复杂度,但提供了更大的灵活性。
4.4 堆内存管理
由于 RIOT OS 设计用于内存有限的嵌入式目标,不鼓励使用动态分配内存。不过,它提供了三种堆内存管理方法:
-
一次性静态分配
:类似于 FreeRTOS 的“Heap 1”模型,内存分配后无法释放。默认情况下,
malloc
函数使用此实现,
free
函数无效。
-
内存数组分配器
:使用静态分配的缓冲区作为固定大小的伪动态分配请求的内存池,适用于处理多个相同大小缓冲区的场景。该分配器有自定义 API,不修改默认
malloc
函数的行为。
-
两级分离适配(TLSF)分配器
:基于为 RTOS 优化的算法,支持多个内存池,提供动态内存支持,同时满足实时截止时间要求。此模块为可选,编译后将替换一次性分配器提供的
malloc
和
free
函数。
以下是 RIOT OS 任务创建与运行的流程图:
graph TD;
A[开始] --> B[分配栈空间];
B --> C[调用 thread_create 创建任务];
C --> D[任务运行];
D --> E{是否主动挂起};
E -- 是 --> F[任务挂起];
F --> G{是否有硬件中断};
G -- 是 --> D;
G -- 否 --> F;
E -- 否 --> H{是否需要内存分配};
H -- 是 --> I[选择内存管理方法];
I --> J[进行内存分配];
J --> D;
H -- 否 --> D;
D --> K{是否结束};
K -- 是 --> L[结束];
K -- 否 --> D;
5. FreeRTOS 与 RIOT OS 的对比
| 特性 | FreeRTOS | RIOT OS |
|---|---|---|
| 设计理念 | 简约,专注于实时调度和堆内存管理 | 提供广泛库和设备支持,注重物联网集成 |
| 任务创建 |
自动分配栈空间,
xTaskCreate
函数
|
需调用者手动分配栈空间,
thread_create
函数
|
| 调度器 | 抢占式,固定优先级 | 无滴答,基于协作 |
| 堆内存管理 | 五种模式,功能丰富 | 三种模式,适配低内存场景 |
| 网络支持 | 需集成第三方 TCP/IP 栈 | 自带 GNRC 极简 IP 栈,支持 lwIP 兼容性层 |
| 低功耗管理 | 默认睡眠模式,可自定义 | 集成电源管理模块,支持多种低功耗模式 |
| 安全性 | 支持 MPU,线程可受限运行 | 可使用 MPU 检测栈溢出 |
通过对比可以看出,FreeRTOS 适合对实时性要求高、追求简单架构的嵌入式应用;而 RIOT OS 更适合物联网场景,尤其是对网络通信和低功耗管理有较高要求的项目。开发者可根据具体需求选择合适的操作系统。
FreeRTOS与RIOT OS系统对比
超级会员免费看
793

被折叠的 条评论
为什么被折叠?



