【stm32_9.2】FreeRTOS的任务管理:任务策略,调度器启用,任务创建、删除、挂起、恢复

目录

[1. FreeRTOS任务的概念](#1. FreeRTOS任务的概念)

[2. FreeRTOS任务的状态](#2. FreeRTOS任务的状态)

[3. FreeRTOS任务优先级](#3. FreeRTOS任务优先级)

[4. FreeRTOS任务的策略](#4. FreeRTOS任务的策略)

[5. 上下文切换](#5. 上下文切换)

[6. FreeRTOS任务的创建](#6. FreeRTOS任务的创建)

任务的创建xTaskCreate的示例

[7. FreeRTOS启动调度器](#7. FreeRTOS启动调度器)

[8. FreeRTOS任务的删除](#8. FreeRTOS任务的删除)

任务删除vTaskDelete的示例

[9. FreeRTOS任务的挂起](#9. FreeRTOS任务的挂起)

[10. FreeRTOS任务的恢复](#10. FreeRTOS任务的恢复)

任务挂起vTaskSuspend和任务恢复vTaskResume的示例

细节汇总


FreeRTOS的任务管理

1. FreeRTOS任务的概念

FreeRTOS 是一个支持多任务的实时操作系统

  • 之前裸机开发时采用的轮询系统而言,主程序是一个死循环,CPU按照死循环中的流程执行代码。
  • 而在多任务系统中,用户可以把整个系统分割为多个独立的且不能返回(死循环)的函数,这些函数就被称为任务。

应用程序中的任务都是由FreeRTOS的调度器进行调度每个任务具有独立的 空间栈空间其实就是单片机中RAM的一段空间,通常可以提前定义一个全局数组,或者在创建任务的时候对任务的栈空间进行动态的分配,可以参考FreeRTOS的官方资料。

调度器 : 内核中负责决定在任何特定时间应执行哪些任务的部分。

调度策略 : 是调度器用来决定在任何时间点执行哪个任务的算法。

重复启停:

做不到并行,所以要频繁的切换任务,来达到每个任务都能跑的效果。

每个任务有自己的堆栈空间,即使任务停止了,处理好的没处理好的,都提前备份好。下一次又执行的时候,再从堆栈空间中的数据恢复回来,再执行一点,再把数据再备份。诸如此类一点一点地更新。

2. FreeRTOS任务的状态

对于FreeRTOS中的任务而言,FreeRTOS的调度器会根据任务的状态决定运行哪个任务,任务的状态一共有四种:运行态、就绪态、挂起态、阻塞态。

如果进入阻塞态和挂起态,CPU会被让渡出去。

3. FreeRTOS任务优先级

FreeRTOS可以为每一个创建的任务分配一个优先级,当然也可以让多个任务使用相同的优先级。

FreeRTOS的任务优先级是没有限制的,并且任务数量也没有限制。

优先级数字越小,则任务的优先级越低。

不要把任务优先级和中断优先级混为一谈。

FreeRTOS只是对应用层做优化,中断是MCU内部资源,中断是照样使用的。

设置时间片轮询的宏定义"configUSE_TIME_SLICING"

4. FreeRTOS任务的策略

FreeRTOS只有两种调度策略:

  1. 抢占式的调度策略
  2. 时间片轮询的调度策略

调度器 : 内核中负责决定在任何特定时间应执行哪些任务的部分。

调度策略 : 是调度器用来决定在任何时间点执行哪个任务的算法。

5. 上下文切换

++在任务挂起时++ 保存任务的上下文,++操作系统内核负责确保上下文保持不变++。

++在任务恢复时++ ,其保存的上下文在执行之前由++操作系统内核恢复++。

6. FreeRTOS任务的创建

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,

const char * const pcName,

const uint16_t usStackDepth,

void * const pvParameters,

UBaseType_t uxPriority,

TaskHandle_t * const pxCreatedTask )

  • 默认处于就绪态

参数

|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| TaskFunction_t pxTaskCode | 任务的地址 |
| const char * const pcName | 任务的描述 |
| const uint16_t usStackDepth | 任务的堆栈大小 (stm32的MCU是32bit的,所有堆栈的宽度是32bit。每个任务的堆栈大小应该由任务的复杂度决定。 堆栈宽度 = 宽度 * 参数大小 " configMINIMAL_STACK_SIZE "任务的堆栈大小不要小于这个宏定义的值 " 130 "。) |
| void * const pvParameters | 任务接口的参数,无需传参则填NULL |
| UBaseType_t uxPriority | 任务的优先级,范围 0 ~ configMAX_PRIORITIES - 1 |
| TaskHandle_t * const pxCreatedTask | 任务的句柄,是为了提供给其他函数使用的(建议把任务的句柄进行存储,可以理解为任务的句柄) |

函数调用的嵌套深度:从一个函数跳到另一个函数,出现嵌套,每嵌套一次都要有一块空间来存储函数的堆栈内容。

uxStackDepth = 堆栈宽度 * 自定义uxStackDepth

堆栈宽度:MCU当前为32bit

任务的创建xTaskCreate的示例

cpp 复制代码
#include "stm32f4xx.h"  //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"

void Task( void * pvParameters )
{
	for(;;)
	{
		//控制灯默认不亮
	GPIO_ResetBits(GPIOF,GPIO_Pin_9);
	}
}

int main()
{
	//1.硬件的初始化
	LED_Config();
	
	BaseType_t xReturned;
	TaskHandle_t xHandle = NULL;			//用于存储创建的任务句柄
	
	//2.创建一个任务
	xReturned = xTaskCreate(
													Task,			//任务的地址
													"Task",		//任务的描述
													64,				//任务的堆栈大小  64 * 32 / 8 = 256字节
													NULL,			//任务接口的参数,无需传参则填NULL
													1, 				//任务的优先级    范围 0 ~ configMAX_PRIORITIES - 1
													&xHandle 	//任务的句柄,是为了提供给其他函数使用的
												);
	//错误处理
	if(xReturned != pdPASS)
	{
		
	}
	
	//3.启动调度器
	vTaskStartScheduler();

	
	while(1)
	{
		
	}
}

7. FreeRTOS启动调度器

void vTaskStartScheduler( void ):

当创建了多个任务,是需要FreeRTOS的调度器利用相关的调度策略对任务进行调度,如果不启动调度器,则任务是不会被执行。

调度器在运行的时候会生成一个空闲任务(优先级为0)

空闲任务不需要手动创建,它在调度器启动时,自动创建空闲任务。

除非内存不足,否则不会执行到 " vTaskStartScheduler() "后面的代码。

一旦调度器启用之后,所有的任务全面由调度器接管,到底执行哪个任务由调度器的调度策略决定,调度策略由优先级决定。

8. FreeRTOS任务的删除

void vTaskDelete( TaskHandle_t xTask ):

被删除的任务将从所有的就绪、阻塞、挂起和事件的列表中移除。

调用vTaskDelect(),则之前申请的空间就会释放掉。但 " 任务代码分配的内存 "(eg.mallco申请的内存)就不会释放,就要在删除之前释放。

参数

xTask 待删除的任务的句柄。传递NULL将导致调用任务被删除

任务删除vTaskDelete的示例

cpp 复制代码
#include "stm32f4xx.h"  //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "USART.h"

TaskHandle_t xHandle1 = NULL;			//用于存储创建的任务句柄
TaskHandle_t xHandle2 = NULL;

void Task1( void * pvParameters )
{
	SendComputerString("task_1 is start\r\n");
	for(;;)
	{
		SendComputerString("task_1 is running\r\n");
		
		vTaskDelete(NULL);
	}
}

void Task2( void * pvParameters )
{
	SendComputerString("task_2 is start\r\n");
	for(;;)
	{
		SendComputerString("task_2 is running\r\n");
		
		vTaskDelete(NULL);
	}
}

int main()
{
	/*现象预判:1.task_2 is start
							2.task_2 is running
							3.task2把自己的任务删除
							4.task_1 is start
							5.task_1 is running
							6.task1把自己的任务删除
	*/
	
	//1.硬件的初始化
	USART1_Config(9600);
	
	//2.创建一个任务
	xTaskCreate(
								Task1,			//任务的地址
								"Task1",		//任务的描述
								64,				//任务的堆栈大小  64 * 32 / 8 = 256字节
								NULL,			//任务接口的参数,无需传参则填NULL
								1, 				//任务的优先级    范围 0 ~ configMAX_PRIORITIES - 1
								&xHandle1 	//任务的句柄,是为了提供给其他函数使用的
							);
	
	xTaskCreate(
								Task2,		
								"Task2",	
								64,			
								NULL,		
								2, 			
								&xHandle2 	
							);
	
	//3.启动调度器
	vTaskStartScheduler();

	
	while(1)
	{
		
	}
}

9. FreeRTOS任务的挂起

void vTaskSuspend( TaskHandle_t xTaskToSuspend ).

  • task. h
  • 必须将 INCLUDE_vTaskSuspend 定义为 1 才能使用此函数
  • 当任务被挂起之后,自己是无法恢复的
  • 对 vaskSuspend 的调用不会累积次数(就是说,对某个任务挂起多次,恢复该任务只要恢复1次即可)

参数

xTaskToSuspend 被挂起的任务句柄。参数为NULL表示挂起自身

10. FreeRTOS任务的恢复

void vTaskResume( TaskHandle_t xTaskToResume ):

  • 必须将 INCLUDE vTaskSuspend 定义为1才能使用此函数。
  • 挂起的次数是不会累加的,所以恢复一次 " 被挂起的任务 " 就行

参数

xTaskToResume 要恢复的任务句柄。

任务挂起vTaskSuspend和任务恢复vTaskResume的示例

cpp 复制代码
#include "stm32f4xx.h"  //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "USART.h"

TaskHandle_t xHandle1 = NULL;			//用于存储创建的任务句柄
TaskHandle_t xHandle2 = NULL;

void Task1( void * pvParameters )
{
	SendComputerString("task_1 is start\r\n");
	for(;;)
	{
		SendComputerString("task_1 is running\r\n");
		
		vTaskResume(xHandle2);
	}
}

void Task2( void * pvParameters )
{
	SendComputerString("task_2 is start\r\n");
	for(;;)
	{
		SendComputerString("task_2 is running\r\n");
		
		vTaskSuspend(NULL);
	}
}

int main()
{
	/*现象预判:1.task_2 is start
							2.task_2 is running
							3.task2把自己挂起了,cpu让渡给task1
							4.task_1 is start
							5.task_1 is running
							6.task1把task2恢复了,task2优先级比task1高且当前系统是抢占式调度策略,task2抢占cpu资源
							7.task_2 is running
							8.task2把自己挂起了,cpu让渡给task1
							9.task_1 is running
							...循环往复
	*/
	
	//1.硬件的初始化
	USART1_Config(9600);
	
	//2.创建一个任务
	xTaskCreate(
								Task1,			//任务的地址
								"Task1",		//任务的描述
								64,				//任务的堆栈大小  64 * 32 / 8 = 256字节
								NULL,			//任务接口的参数,无需传参则填NULL
								1, 				//任务的优先级    范围 0 ~ configMAX_PRIORITIES - 1
								&xHandle1 	//任务的句柄,是为了提供给其他函数使用的
							);
	
	xTaskCreate(
								Task2,		
								"Task2",	
								64,			
								NULL,		
								2, 			
								&xHandle2 	
							);
	
	//3.启动调度器
	vTaskStartScheduler();

	
	while(1)
	{
		
	}
}

细节汇总

FreeRTOS操作系统要移植到MCU上,MCU上有内核,内核和RTOS做了整合,确保可以正常运行操作系统。

FreeRTOS使用FreeRTOSConfig.h进行定制。

工程在运行的时候是要先运行汇编文件的 " xxx.s ",再从汇编文件跳转到main函数里。

FreeRTOSConfig.h和应用程序(程序/代码)有关,在内核源码RTOS中是找不到的。

这个头文件不能放在 " 与FreeRTOS相关的目录 " 里,必须放在自己的应用程序目录

Linux系统下创建的多线程,是为了用线程去访问进程中的资源。

任务的运行是需要资源的。

把创建的任务交给系统的调度器(管家)。

多任务系统通过调度器和调度策略,可以确保这些任务都能在规定的时间内完成处理和响应。

要搭载"多任务系统"需要调度器,操作系统提供调度器。==> 所以要在MCU上移植操作系统。

抢占式的调度策略无法并发 ,为了不一直运行优先级最高的任务,应该人为的挂起或阻塞最高的优先级,把CPU让渡出来

重用某个任务,让它的优先级最高,但为了让其他任务也能执行,→ 所以给最高优先级的任务使用 "挂起" 或 "延时阻塞".

①"挂起" 自己,但自己无法让自己 "恢复"到就绪态,而其他任务不一定会帮最高优先级task恢复,所以挂起有风险。

②"延时阻塞" 可以把CPU资源让出去一段时间,时间到了,最高优先级task从 "阻塞" 回答 "就绪"。

优先级相同的就绪任务,RTOS采用时间片轮询的调度策略。

优先级不同的就绪任务,RTOS采用抢占式的调度策略。

上下文切换:任务在执行的时候要访问内存空间RAM和ROM、寄存器、堆栈空间等...

每个任务在自己的上下文执行 ==> 每个任务都有自己的堆栈

Q :重复启停每个任务

A :任务有自己的堆栈空间,就算任务现在停止了,就可以把处理好的、没处理好的提前先备份好。等到下一次又能够执行时,在从堆栈空间中把数据恢复回来再执行一点,再把数据备份,在到下次执行把数据从堆栈里读取出来。

优先级低的任务正在运行态,优先级高的任务是就绪态,当前算法是 "抢占式调度策略",此时 "优先级高的任务" 会 "抢占优先级低的任务" 的CPU资源,就算 "优先级低的时间片" 还没用完。

Q :单核MCU,如何让多个任务同时运行?

A :利用调度器,对任务进行频繁的切换。

Q :切换的原则 / 以什么样的规则去切换?

A :调度策略:抢占式调度策略、时间片的调度策略

任务都有优先级,FreeRTOS对任务的数量和优先级没有限制,所以如果使用抢占式的调度策略,我可以给每个任务分配一个任务优先级。

如果使用时间片轮询的调度策略,我可以给任务分配相同的任务优先级。

Q :优先级的规则是什么?

A :在FreeRTOS中的任务优先级是 "优先级的数值越小,则任务的优先级越低"。

Q :如何在MCU上移植FreeRTOS?

A :上章有写

Q :FreeRTOS的功能是如何使用的?

A :FreeRTOS是使用配置文件对实时操作系统进行定制的。

"FreeRTOSConfig.h" ,要启用或禁用某些功能,FreeRTOS提供了若干的宏定义,在"FreeRTOSConfig.h"中去设置宏定义的值,如果设置为0,则表示禁用,非0则表示启用。

Q :为什么通过宏定义就实现了?

A :条件编译。

相关推荐
是阿建吖!17 小时前
【Linux】信号
android·linux·c语言·c++
Aaron158818 小时前
无人机反制中AOA+TDOA联合定位技术与雷达探测定位技术的应用对比分析
arm开发·嵌入式硬件·fpga开发·硬件工程·无人机·信息与通信·信号处理
三品吉他手会点灯18 小时前
C语言学习笔记 - 43.运算符与表达式 - 运算符1 - 运算符的分类和简单介绍
c语言·笔记·学习·算法
foundbug99918 小时前
STM32 睡眠模式测试程序
stm32·单片机·嵌入式硬件
wxmtwfx20 小时前
littlefs 源码分析
单片机·littlefs·嵌入式文件系统
wuminyu21 小时前
Java锁机制之轻量级锁判断与尝试逻辑源码剖析
java·linux·c语言·jvm·c++
嵌入式小站21 小时前
STM32 零基础可移植教程 18:I2C 入门,先用扫描器找一找总线上有没有设备
chrome·stm32·嵌入式硬件
天涯铭1 天前
深入浅出:单片机I/O口串联电阻选型
单片机·嵌入式硬件·io口串联电阻
国科安芯1 天前
ASP7A84AS——航天级低噪声高PSRR线性稳压器
网络·单片机·嵌入式硬件·架构·安全性测试
老H科研技术1 天前
第 01 篇:MCP 概念与架构 —— AI 世界的“USB-C“
c语言·人工智能·chatgpt·架构·aigc·agi