上一节通过系统架构综合项目,把任务、队列以及任务优先级真正组合到了一个完整程序中。
到这里,我们已经会“使用”FreeRTOS了。
但是还有一个很重要的问题:
FreeRTOS为什么能让多个任务轮流运行?
STM32F407明明只有一个Cortex-M4内核,却可以表现得像:
Task1在运行
Task2也在运行
Task3也在运行
实际上CPU同一时刻只能执行一个任务。FreeRTOS只是通过快速保存和恢复任务运行现场,让CPU不断在多个任务之间切换。
本节学习目标:初步理解FreeRTOS在STM32F407上的移植结构,以及SysTick、PendSV和SVC在任务调度中的作用。不深入分析汇编源码,先建立整体认识。
一、什么叫FreeRTOS移植
FreeRTOS可以运行在:
Cortex-M0
Cortex-M3
Cortex-M4
Cortex-M7
RISC-V
……
但是不同CPU:
寄存器不同
中断机制不同
栈结构不同
任务切换方法不同
所以FreeRTOS不可能只靠完全相同的一份底层代码运行在所有处理器上。
因此FreeRTOS把:
通用功能
和:
处理器相关功能
进行了分离。
例如:
tasks.c
queue.c
list.c
主要属于FreeRTOS通用内核代码。
而STM32F407使用Cortex-M4,因此还需要对应Cortex-M4的:
port.c
portmacro.h
这部分就是:
FreeRTOS移植层。
二、port.c有什么作用
前面学习BaseType_t时,我们已经接触过:
portmacro.h
STM32F407对应的移植层会处理很多和Cortex-M4直接相关的内容。
例如:
任务栈初始化
中断优先级处理
临界区
启动第一个任务
任务上下文切换
所以可以简单理解:
FreeRTOS内核
↓
port.c
↓
Cortex-M4硬件
FreeRTOS内核负责决定:
下一个应该运行哪个任务
而移植层负责解决:
在Cortex-M4上到底怎样完成切换。
三、什么是任务切换
假设现在有:
Task1
Task2
CPU当前正在运行Task1:
CPU
↓
Task1
Task1运行到:
vTaskDelay(
pdMS_TO_TICKS(100)
);
Task1需要阻塞。
FreeRTOS于是选择Task2:
Task1
↓
保存运行现场
↓
选择Task2
↓
恢复Task2运行现场
↓
Task2继续运行
这里所谓的:
运行现场
可以简单理解为任务当前的:
CPU寄存器
栈指针
程序执行位置
等信息。
这个过程就叫:
上下文切换
也就是Context Switch。
四、为什么任务都有自己的栈
前面学习任务栈时,我们知道每个任务都有自己的栈。
现在就能进一步理解原因了。
假设:
Task1 Stack
Task2 Stack
Task1被切换出去时:
Task1当前运行状态
↓
保存到Task1自己的栈
以后重新运行Task1:
Task1 Stack
↓
恢复CPU状态
↓
Task1继续执行
所以任务切换并不是:
Task1重新从函数开头运行。
而是:
从之前停止的位置继续运行。
这就是为什么:
void Task1(void *pvParameters)
{
while(1)
{
LED1_Toggle();
vTaskDelay(
pdMS_TO_TICKS(500)
);
}
}
每次Task1重新获得CPU以后,可以继续正常执行。
五、SysTick负责什么
STM32F407的Cortex-M4内核提供:
SysTick
FreeRTOS通常可以利用它产生系统节拍。
假设:
#define configTICK_RATE_HZ 1000
那么:
每1ms产生一次Tick
系统Tick到来以后,FreeRTOS需要处理:
任务延时时间
阻塞时间
软件定时器相关时间
是否有任务需要解除阻塞
例如Task1:
vTaskDelay(
pdMS_TO_TICKS(100)
);
可以简单理解成:
Task1阻塞
↓
Tick不断计时
↓
100ms到达
↓
Task1重新进入Ready
所以SysTick主要解决:
FreeRTOS的时间节拍问题。
六、PendSV负责什么
真正进行任务上下文切换时,Cortex-M系列中一个非常重要的异常就是:
PendSV
可以简单记成:
SysTick
负责时间
PendSV
负责任务切换
例如:
SysTick到来
↓
发现高优先级Task2已经到时间
↓
需要进行任务切换
↓
触发PendSV
↓
保存Task1现场
↓
恢复Task2现场
↓
Task2运行
FreeRTOS通常会把PendSV设置成很低的中断优先级。
这样做的目的之一就是:
尽量等其他需要及时处理的中断结束
↓
再进行任务上下文切换。
七、SVC又是干什么的
除了:
SysTick
PendSV
还经常会看到:
SVC
也就是:
Supervisor Call
在Cortex-M的FreeRTOS移植中,它与启动调度过程有关。
例如程序执行:
vTaskStartScheduler();
以后,FreeRTOS最终需要:
启动第一个任务。
可以简单理解成:
vTaskStartScheduler()
↓
配置调度相关硬件
↓
SVC参与启动第一个任务
↓
恢复任务现场
↓
第一个任务开始运行
初学阶段不用深入SVC汇编代码,只需要先记住:
SVC
和FreeRTOS启动第一个任务有关。
八、三个异常怎么记
学习到这里最容易混淆的就是:
SysTick
PendSV
SVC
可以先这样记:
SysTick
↓
提供系统时间节拍
PendSV
↓
完成任务上下文切换
SVC
↓
参与启动第一个任务
整个过程可以粗略理解为:
vTaskStartScheduler()
↓
SVC
↓
第一个任务运行
↓
SysTick周期到来
↓
FreeRTOS进行时间管理
↓
发现需要切换任务
↓
PendSV
↓
保存当前任务
↓
恢复下一个任务
掌握这条主线就足够了。
九、为什么我们平时感觉不到任务切换
假设:
Task1运行
↓
切换Task2
↓
切换Task3
↓
再次Task1
这个过程非常快。
所以从人的观察角度来看:
LED1在闪
LED2也在闪
串口还在发送数据
看起来好像:
所有任务同时运行。
实际上对于STM32F407这种单核MCU:
同一时刻
只有一个任务真正占用CPU。
FreeRTOS只是不断进行:
保存现场
↓
切换任务
↓
恢复现场
从而实现我们看到的多任务运行效果。
十、总结
这一节初步学习了FreeRTOS移植与任务调度原理。
FreeRTOS能够运行在STM32F407上,除了通用内核:
tasks.c
queue.c
list.c
还需要与Cortex-M4相关的:
port.c
portmacro.h
任务切换的核心过程:
当前任务运行
↓
保存任务现场
↓
选择下一个任务
↓
恢复任务现场
↓
新任务运行
而STM32F407上的三个重要异常可以先记成:
SysTick
↓
系统时间节拍
PendSV
↓
任务上下文切换
SVC
↓
参与启动第一个任务
因此FreeRTOS所谓的:
多任务同时运行
对于单核STM32来说,本质上其实是:
CPU在多个任务之间快速切换。
到这里,我们不仅会使用:
xTaskCreate();
vTaskDelay();
xQueueSend();
xSemaphoreTake();
也开始知道这些API背后最终是怎样依靠Cortex-M4硬件完成任务调度的。
——FreeRTOS移植与调度原理&spm=1001.2101.3001.5002&articleId=163950296&d=1&t=3&u=569296cf78344a3480820a16e0d1e17f)
7219

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



