Skip to main content

裸机任务调度机制的实现

任务调度

实时多任务操作系统(RTOS)是嵌入式应用软件的基础和开发平台,简单易用且开源,被多数嵌入式开发者使用。但在简单且实时性要求较强的情况中,RTOS 因系统损耗而无法使用。此时时间片轮询法受青睐,它能模仿任务调度系统,实现简单任务调度且可基于 STM32 裸机系统运行。在 STM32 裸机系统中常利用其定时器系统,根据执行周期为任务函数设定不同频率,从而实现简单任务调度。

嵌入式基础知识-系统调度_嵌入式

接下来介绍最简单的时间片轮询机制

时间片轮询机制

时间片轮询机制的基本步骤

  1. 任务定义:定义需要执行的多个任务,每个任务是一个独立的函数。
  2. 时间片设置:为每个任务分配一个时间片,用于控制每个任务执行的时间。
  3. 调度器实现:通过定时器或者其他触发机制,依次执行任务函数。
  4. 时间片轮转:任务按照一定的顺序执行,执行完一个任务后调度下一个任务,直到所有任务完成一个轮询。

实现方法

假设我们使用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. 优先级调度机制

在实际应用中,任务的紧急程度往往不同,使用优先级调度机制可以保证更重要的任务优先得到处理。任务调度器根据任务的优先级来决定执行哪个任务,而不是依赖固定的时间片。

优点

  • 高优先级任务得到及时响应。
  • 对时间敏感的任务处理更加有效。

实现思路

  1. 给每个任务分配一个优先级。
  2. 每次调度时,选择优先级最高的任务进行执行。
  3. 可以手动管理任务执行的时机,例如用定时器来触发调度。

实现示例

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空转的时间,节省功耗。

优点

  • 提高了任务调度的效率,避免无效的轮询。
  • 在没有事件时,系统可以进入低功耗模式。

实现思路

  1. 每个任务等待某个事件(例如中断、定时器、GPIO输入等)。
  2. 当事件发生时,触发相关任务执行。
  3. 利用外部中断或定时器来唤醒系统执行任务。

实现示例

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控制权。这种机制允许任务在执行完一段关键代码后,主动调用调度器进行下一次任务的执行。

优点

  • 任务之间切换开销小。
  • 不需要依赖中断或定时器,适合任务粒度较大的系统。

实现思路

  1. 任务完成某段工作后主动调用调度器。
  2. 调度器选择下一个任务执行。

实现示例

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. 空闲任务机制

对于有较长时间等待的系统,可以添加一个低优先级的“空闲任务”,当没有其他任务需要执行时,系统进入空闲任务,这种机制可以配合低功耗模式使用。

优点

  • 节省资源,降低系统功耗。
  • 在嵌入式系统中非常实用,尤其适合低功耗应用。

实现思路

  1. 定义一个最低优先级的空闲任务。
  2. 当没有其他任务需要执行时,系统进入空闲任务。
c复制代码void IdleTask(void) {
// 系统空闲时可以进入低功耗模式
__WFI(); // 等待中断模式,降低功耗
}

void Scheduler(void) {
// 检查是否有任务需要执行,如果没有,则执行空闲任务
if (eventFlag1 || eventFlag2) {
// 其他任务调度逻辑
} else {
IdleTask(); // 执行空闲任务
}
}

总结:

  • 时间片轮询因为简单适合小型、低复杂度的系统。
  • 优先级调度可确保时间敏感或关键任务优先处理,适合多任务系统。
  • 基于事件的调度通过事件触发减少了CPU空转,适合低功耗应用。
  • 循环调度降低了任务间切换的开销,适用于任务独立的场景。
  • 空闲任务机制最大化了功耗管理,适合节能需求高的系统。