嵌入式操作系统:系统资源分离与多任务调度
1. 系统资源分离
为了限制当前任务直接访问资源,采取了进一步的限制措施。示例系统通过以下代码设置了内存保护单元(MPU):
DMB();
mpu_set_region(3, (uint32_t)start, attr);
MPU_CTRL = 1;
这些限制使得任务无法直接控制输入输出外设等系统资源,甚至
sleep_ms
函数也不能设置挂起标志来发起上下文切换。为了维持原有功能,系统需要导出新的安全 API 供任务请求系统操作。
2. 系统调用
操作系统通过 SVCall 异常的系统调用机制导出 API,该异常由
isr_svc
服务例程处理,任务可通过
svc
指令随时触发。示例中使用
svc 0
汇编指令切换到处理模式,并定义了快捷宏
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 | 打开蓝色 LED |
| SYS_BLUELED_OFF | 3 | 关闭蓝色 LED |
| SYS_BLUELED_TOGGLE | 4 | 切换蓝色 LED 状态 |
| SYS_REDLED_ON | 5 | 打开红色 LED |
| SYS_REDLED_OFF | 6 | 关闭红色 LED |
| SYS_REDLED_TOGGLE | 7 | 切换红色 LED 状态 |
| SYS_GREENLED_ON | 8 | 打开绿色 LED |
| SYS_GREENLED_OFF | 9 | 关闭绿色 LED |
| SYS_GREENLED_TOGGLE | 10 | 切换绿色 LED 状态 |
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;
}
}
}
3. 嵌入式操作系统选择
选择适合目标平台和用途的嵌入式操作系统是一项复杂的任务,需要考虑硬件特性、与第三方库的集成、外设交互能力以及系统的使用场景等因素。常见的嵌入式操作系统功能套件包括:
- 特定平台的硬件抽象层
- 常见外设的设备驱动
- 用于连接性的 TCP/IP 栈集成
- 文件系统和文件抽象
- 集成电源管理系统
根据调度器中线程模型的实现,有些系统在编译时配置固定数量的任务,而有些则支持在运行时创建和终止线程。但在嵌入式系统中,动态任务创建和终止的需求较少。
4. 流行的开源嵌入式操作系统
4.1 FreeRTOS
FreeRTOS 是嵌入式设备中最流行的开源操作系统之一,具有高度的可移植性,支持多种嵌入式 CPU 架构。它专注于线程的实时调度和堆内存管理,代码简洁,接口简单。
- 调度器 :采用抢占式调度,具有固定优先级和通过共享互斥锁的优先级继承机制。线程的优先级和栈空间大小在创建时确定。
xTaskCreate(task_entry_fn, "TaskName", task_stack_size,
( void * ) custom_params, priority, task_handle);
创建任务后,调用
vTaskStartScheduler()
启动调度器。
-
堆内存管理
:提供五种堆内存管理模型:
- Heap 1:仅允许一次性静态分配,无法释放内存。
- Heap 2:允许释放内存,但不重新组合已释放的块。
-
Heap 3:封装第三方库的
malloc/free实现,确保线程安全。 - Heap 4:支持内存合并,减少堆碎片化。
- Heap 5:与 Heap 4 类似,但允许定义多个非连续的内存区域作为同一堆空间。
选择特定的堆模型只需包含相应的源文件,如
heap_1.c
、
heap_2.c
等。重要的堆内存管理函数是
pvPortMalloc
和
pvPortFree
。
- 其他特性 :支持 MPU 和线程模式,线程可在受限模式下运行;低功耗管理限于睡眠模式,但允许重新定义调度器回调函数进入自定义低功耗模式;近期版本包含第三方代码,用于构建物联网安全连接平台。
graph LR
A[创建任务] --> B[启动调度器]
B --> C[任务调度]
C --> D{任务状态}
D -->|运行| E[执行任务]
D -->|就绪| F[等待调度]
D -->|阻塞| G[等待资源]
嵌入式操作系统:系统资源分离与多任务调度
5. RIOT OS
RIOT OS 主要构建在受限微控制器上,适用于低功耗嵌入式系统,如物联网项目和长期运行无需维护的场景。它旨在以最少的资源组织和同步任务,同时注重节能和连接性。
5.1 系统特点
- 丰富的库和设备支持 :与 FreeRTOS 的极简主义不同,RIOT OS 提供了广泛的库和设备支持代码,包括网络栈和无线驱动通信,对物联网应用友好。非核心功能的组件分为可选模块,通过自定义的基于 makefile 的构建系统方便集成到应用中。
- 类 POSIX 接口 :从 API 角度,RIOT 社区尽量模仿 POSIX 接口,方便不同背景的程序员使用标准 C 语言 API 访问系统资源。不过,系统采用扁平模型,未在系统层面实现特权分离,用户空间应用仍需直接引用系统内存来访问资源。
- MPU 安全措施 :为提高安全性,可使用 MPU 检测单线程的栈溢出。在栈底部设置一个小的只读区域,若线程试图写入超出分配的栈空间,将触发异常。
5.2 网络通信
RIOT OS 实现了多个通信栈作为模块,其中包括专为 802.15.4 网络定制的 IPv6 极简 IP 栈 GNRC,并提供了套接字实现,用于编写轻量级物联网应用。此外,还包含 lwIP 兼容性层,可在需要时提供更完整的 TCP/IP 实现。同时,WolfSSL 模块可用于实现安全套接字通信和数据加密。
5.3 低功耗管理
通过电源管理模块,RIOT OS 集成了低功耗模式的配置功能。该模块提供了管理特定平台功能的抽象层,如 Cortex - M 平台的停止和待机模式。应用程序可在运行时激活低功耗模式,并利用实时时钟、看门狗定时器或其他外部信号返回正常运行模式。
5.4 调度器
RIOT OS 的调度器是无滴答且基于协作的。任务可通过调用
task_yield
函数或任何阻塞函数(如访问内核功能或硬件外设)来显式挂起自己。系统不会基于时间片强制并发,只有在接收到硬件中断时才会强制中断任务。因此,使用该调度器编程时需特别注意,避免在一个任务中意外创建忙循环,导致整个系统饥饿。
任务可通过
thread_create
函数创建:
thread_create(task_stack_mem, task_stack_size, priority,
flags, task_entry_fn, (void*)custom_args, "TaskName");
与 FreeRTOS 不同,调用者必须为创建任务的栈空间分配内存,这增加了调用者的代码量,但也提供了更多自定义栈空间位置的灵活性。由于调度器无滴答,无需手动启动,任务可在执行过程中的任何时间创建和停止。
5.5 堆内存管理
考虑到嵌入式目标设备的 RAM 有限,RIOT OS 不鼓励使用动态分配的内存,但提供了三种堆内存管理方法:
| 管理方法 | 特点 |
| ---- | ---- |
| 一次性静态分配 | 类似于 FreeRTOS 的“Heap 1”模型,分配的内存无法释放或重用。默认情况下,
malloc
函数使用此实现,
free
函数无效。 |
| 内存数组分配器 | 使用静态分配的缓冲区作为内存池,用于固定大小的伪动态分配请求。适用于处理多个相同大小缓冲区的场景,有自定义 API,不修改默认
malloc
函数的行为。 |
| 两级分离适配(TLSF)分配器 | 基于为 RTOS 优化的算法,支持多个内存池,提供动态内存支持并处理实时截止日期。作为可选模块,编译时会替换一次性分配器提供的
malloc
和
free
函数。 |
6. 总结
不同的嵌入式操作系统适用于不同的应用场景。FreeRTOS 以其简单的设计、高度的可移植性和丰富的堆内存管理选项,适合对实时调度和内存管理有严格要求的应用。而 RIOT OS 则凭借其对低功耗和物联网连接性的关注,以及丰富的库和设备支持,成为低功耗嵌入式系统和物联网项目的理想选择。在选择嵌入式操作系统时,开发者应根据具体的硬件平台、应用需求和性能要求进行综合考虑。
graph LR
A[启动 RIOT OS] --> B[创建任务]
B --> C[任务执行]
C --> D{任务状态}
D -->|运行| E[执行任务代码]
D -->|挂起| F[等待唤醒]
D -->|阻塞| G[等待资源或事件]
E --> H{是否有硬件中断}
H -->|是| I[中断任务]
H -->|否| C
I --> J[处理中断]
J --> C
超级会员免费看

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



