裸机任务调度机制的实现
任务调度
实时多任务操作系统(RTOS)是嵌入式应用软件的基础和开发平台,简单易用且开源,被多数嵌入式开发者使用。但在简单且实时性要求较强的情况中,RTOS 因系统损耗而无法使用。此时时间片轮询法受青睐,它能模仿任务调度系统,实现简单任务调度且可基于 STM32 裸机系统运行。在 STM32 裸机系统中常利用其定时器系统,根据执行周期为任务函数设定不同频率,从而实现简单任务调度。
接下来介绍最简单的时间片轮询机制
时间片轮询机制
时间片轮询机制的基本步骤
- 任务定义:定义需要执行的多个任务,每个任务是一个独立的函数。
- 时间片设置:为每个任务分配一个时间片,用于控制每个任务执行的时间。
- 调度器实现:通过定时器或者其他触发机制,依次执行任务函数。
- 时间片轮转:任务按照一定的顺序执行,执行完一个任务后调度下一个任务,直到所有任务完成一个轮询。
实现方法
假设我们使用STM32的SysTick定时器来实现任务调度,每次定时器中断发生时,任务调度器会切换到下一个任务。
1. 定义任务函数
c复制代码void Task1(void) {
// Task1逻辑
}
void Task2(void) {
// Task2逻辑
}
void Task3(void) {
// Task3逻辑
}
2. 定义任务调度器
使用一个简单的轮询机制来管理任务切换:
c复制代码#define NUM_TASKS 3 // 任务数量
void (*taskArray[NUM_TASKS])(void) = {Task1, Task2, Task3}; // 任务数组
volatile uint32_t currentTask = 0; // 当前任务编号
void Scheduler(void) {
// 轮询任务
taskArray[currentTask](); // 执行当前任务
// 切换到下一个任务
currentTask++;
if (currentTask >= NUM_TASKS) {
currentTask = 0; // 任务编号回绕
}
}
3. SysTick定时器配置
SysTick 是一个系统定时器,是 ARM Cortex - M 内核中的一个基本组件,存在于基于该内核的微控制器中,比如 STM32 系列。本质上是一个 24 位的向下递减计数器。当计数器的值从初始值递减到 0 时,会产生一个中断(SysTick 中断),并且可以根据设置自动重装初始值继续计数。在实时操作系统(RTOS)中,SysTick 通常被用作系统的心跳定时器。操作系统通过 SysTick 中断来进行任务调度、时间片管理等操作,确保系统中各个任务能够按照预定的规则和时间片进行执行。例如,每一次 SysTick 中断发生时,操作系统可以检查各个任务的状态,决定是否进行任务切换。
利用SysTick定时器触发任务调度器:
c复制代码void SysTick_Handler(void) {
Scheduler(); // 在SysTick中断中调用调度器
}
void SysTick_Init(uint32_t ticks) {
// 初始化SysTick定时器
SysTick_Config(ticks);
}
4. 主函数初始化
c复制代码int main(void) {
// 初始化SysTick,假设每1ms触发一次调度
SysTick_Init(SystemCoreClock / 1000);
while (1) {
// 主循环空转,由SysTick定时器触发任务调度
}
}
其他的任务调度机制
除了时间片轮询还有没有其他机制呢,也是有的
调度机制 | 优点 | 优点的原因 | 缺点 | 适用场景 | 实现复杂度 |
---|---|---|---|---|---|
时间片轮询 | 简单易实现,适合任务数量少、时间片固定的场景。 | 只需要依次执行任务,代码逻辑简单,调度器维护少,适合资源受限的环境。 | 任务不能按优先级处理,所有任务得到同样的处理时间,资源利用率低。 | 简单系统、任务数量较少的场景。 | 低 |
优先级调度 | 任务根据优先级进行处理,保证高优先级任务优先执行,适应性强。 | 能确保关键任务或时间敏感任务优先处理,减少延迟,提高系统的响应性。 | 需要设计和维护优先级,可能导致低优先级任务长期得不到执行。 | 多任务系统,任务有不同优先级需求,关键任务需要优先处理。 | 中等 |
基于事件的调度 | 高效响应事件,不需要持续轮询,降低功耗。 | 只有在事件发生时才执行任务,CPU大部分时间处于空闲状态,适合低功耗应用。 | 对任务的实时性要求较高,任务之间的调度受事件影响。 | 事件驱动型系统,如传感器系统、输入响应型嵌入式系统。 | 中等 |
循环调度 | 任务主动让出CPU,节省切换开销,简单灵活。 | 任务自己决定何时让出CPU,减少上下文切换带来的开销,同时调度器无需干预任务的执行。 | 任务之间依赖于各自的让出CPU,容易导致某任务长时间占用CPU资源。 | 任务粒度较大且彼此独立的系统。 | 低 |
空闲任务机制 | 在无任务时可进入低功耗模式,节约系统能耗。 | 系统空闲时进入低功耗模式(如__WFI 等待中断指令),使CPU利用率最大化,减少电源消耗。 | 需要设置适当的空闲任务,当任务过多时空闲任务执行的机会较少。 | 低功耗系统、任务执行间隙较长的场景。 | 低 |
1. 优先级调度机制
在实际应用中,任务的紧急程度往往不同,使用优先级调度机制可以保证更重要的任务优先得到处理。任务调度器根据任务的优先级来决定执行哪个任务,而不是依赖固定的时间片。
优点:
- 高优先级任务得到及时响应。
- 对时间敏感的任务处理更加有效。
实现思路:
- 给每个任务分配一个优先级。
- 每次调度时,选择优先级最高的任务进行执行。
- 可以手动管理任务执行的时机,例如用定时器来触发调度。
实现示例:
c复制代码typedef struct {
void (*taskFunc)(void); // 任务函数指针
uint8_t priority; // 优先级,数值越大优先级越高
} Task;
#define NUM_TASKS 3
Task taskList[NUM_TASKS] = {
{Task1, 1}, // 优先级1
{Task2, 3}, // 优先级3
{Task3, 2} // 优先级2
};
void Scheduler(void) {
uint8_t highestPriority = 0;
uint8_t highestPriorityTask = 0;
// 找到优先级最高的任务
for (uint8_t i = 0; i < NUM_TASKS; i++) {
if (taskList[i].priority > highestPriority) {
highestPriority = taskList[i].priority;
highestPriorityTask = i;
}
}
// 执行优先级最高的任务
taskList[highestPriorityTask].taskFunc();
}
2. 基于事件的任务调度
在很多嵌入式应用中,任务并不需要持续运行,而是等待某种事件的触发。事件驱动调度机制是基于外部或内部的事件(如中断、传感器输入等)来决定任务的执行,能够减少CPU空转的时间,节省功耗。
优点:
- 提高了任务调度的效率,避免无效的轮询。
- 在没有事件时,系统可以进入低功耗模式。
实现思路:
- 每个任务等待某个事件(例如中断、定时器、GPIO输入等)。
- 当事件发生时,触发相关任务执行。
- 利用外部中断或定时器来唤醒系统执行任务。
实现示例:
c复制代码volatile uint8_t eventFlag1 = 0; // 事件标志1
volatile uint8_t eventFlag2 = 0; // 事件标志2
void EXTI0_IRQHandler(void) {
eventFlag1 = 1; // 设置事件1标志
// 清除中断标志
}
void TIM2_IRQHandler(void) {
eventFlag2 = 1; // 设置事件2标志
// 清除定时器中断标志
}
void Scheduler(void) {
if (eventFlag1) {
Task1(); // 事件1触发Task1
eventFlag1 = 0;
}
if (eventFlag2) {
Task2(); // 事件2触发Task2
eventFlag2 = 0;
}
}
3. 循环调度(Cooperative Scheduling)
在循环调度中,任务不会被强制切换,而是通过任务自身的逻辑主动让出CPU控制权。这种机制允许任务在执行完一段关键代码后,主动调用调度器进行下一次任务的执行。
优点:
- 任务之间切换开销小。
- 不需要依赖中断或定时器,适合任务粒度较大的系统。
实现思路:
- 任务完成某段工作后主动调用调度器。
- 调度器选择下一个任务执行。
实现示例:
c复制代码volatile uint8_t taskIndex = 0;
void Scheduler(void) {
taskArray[taskIndex](); // 执行当前任务
taskIndex++;
if (taskIndex >= NUM_TASKS) {
taskIndex = 0; // 回绕任务索引
}
}
void Task1(void) {
// 任务1的逻辑
// 任务结束后主动切换到下一个任务
Scheduler();
}
void Task2(void) {
// 任务2的逻辑
Scheduler();
}
void Task3(void) {
// 任务3的逻辑
Scheduler();
}
4. 空闲任务机制
对于有较长时间等待的系统,可以添加一个低优先级的“空闲任务”,当没有其他任务需要执行时,系统进入空闲任务,这种机制可以配合低功耗模式使用。
优点:
- 节省资源,降低系统功耗。
- 在嵌入式系统中非常实用,尤其适合低功耗应用。
实现思路:
- 定义一个最低优先级的空闲任务。
- 当没有其他任务需要执行时,系统进入空闲任务。
c复制代码void IdleTask(void) {
// 系统空闲时可以进入低功耗模式
__WFI(); // 等待中断模式,降低功耗
}
void Scheduler(void) {
// 检查是否有任务需要执行,如果没有,则执行空闲任务
if (eventFlag1 || eventFlag2) {
// 其他任务调度逻辑
} else {
IdleTask(); // 执行空闲任务
}
}
总结:
- 时间片轮询因为简单适合小型、低复杂度的系统。
- 优先级调度可确保时间敏感或关键任务优先处理,适合多任务系统。
- 基于事件的调度通过事件触发减少了CPU空转,适合低功耗应用。
- 循环调度降低了任务间切换的开销,适用于任务独立的场景。
- 空闲任务机制最大化了功耗管理,适合节能需求高的系统。