任务
一. 任务的基本概念
-
FreeRTOS的任务可以认为是一系列独立的任务的集合。 -
每个任务在自己的环境中运行,并且每个时刻只有一个任务在运行,但从宏观上看,所有的任务都在同时执行; 不同任务的切换与任务本身无关,这是由调度器来实现的。调度器负责在任务切入、切出时保存上下文环境(寄存器值、栈内容); 所以每个任务都有自己独立的栈空间,任务切出时,其执行环境会被保存在该任务的栈空间中,当任务再次运行时,就能从栈中恢复上次的运行环境。 -
任务越多,需要的栈空间就越大,一个系统能运行多少个任务,取决于系统可用的SRAM。 -
任务采用抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞后才能得到调度, FreeRTOS也支持时间片轮转调度方式,只不过时间片的调度不允许抢占任务的CPU使用权。 -
任务通常运行在一个死循环中,也不会退出,如果不需要某个任务,可以调用删除任务API函数将其删除。 -
任务里面的延时函数必须使用 FreeRTOS 里面提供的延时函数,并不能使用我们裸机编程中的那种延时。 这两种的延时的区别: FreeRTOS 里面的延时是阻塞延时,即调用 vTaskDelay()函数的时候,当前任务会被起,调度器会切换到其它就绪的任务,从而实现多任务。 如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务。
二. 任务调度器的基本概念
-
FreeRTOS中提供的任务调度器是基于优先级的全抢占调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码不可抢占之外, 系统的其他部分都是可以抢占的。 -
FreeRTOS内核中采用两种方法寻找最高优先级的任务,一般和特殊。 -
FreeRTOS内核中允许创建相同优先级的任务。相同优先级的任务采用时间片轮转方式进行调度(分时调度器), 时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。 -
任务调度的原则:一旦任务状态发生改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时,立刻进行任务切换,(除非当前系统正处于中断处理程序中或禁止任务切换的状态)。
三. 任务状态的概念
- 就绪(ready)态: 该任务在就绪列表中,就绪的任务已经具备执行能力,只等待调度器进行调度,新创建的任务会被初始化为就绪态。
- 运行(running)态: 该状态表明任务正在执行,此时它占用处理器,FreeRTOS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务开始运行的一刻,就变成了运行态。
- 阻塞(blocked)态: 如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞态,该任务不在就绪列表中,包含任务挂起、任务延时、任务正在等待信号量、读写队列或者等待读写事件等。
- 挂起(suspended)态: 处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起态的唯一办法就是调用vTaskSuspend()函数;
而恢复一个挂起态的任务的唯一途径是调用vTaskResum()或vTaskResumFromISR()。
四. 创建一个LED任务
? 首先一定要有一个空闲任务,空闲任务是 FreeRTOS 系统自己启动的一个任务,优先级最低。当整个系统都没有就绪任务的时候,系统必须保证有一个任务在运行,空闲任务就是为这个设计的。当用户任务延时到期,又会从空闲任务切换回用户任务。如果当系统中始终有一个在运行的任务时,可以不加空闲任务。
? 一个任务的三要素是任务主体函数,任务栈,任务控制块。FreeRTOS 里面有一个叫动态任务创建函数 xTaskCreate(),它将任务主体函数,任务栈(动态的)和任务控制块(动态的)这三者联系在一起,让任务可以随时被系统启动。
创建任务的步骤
- 定义任务函数
static void LED_Task(void* parameter)
{
while(1)
{
LED1_ON;
vTaskDelay(500);
printf("LED_Task Running,LED1_ON\r\n");
LED1_OFF;
vTaskDelay(500);
printf("LED_Task Running,LED1_OFF\r\n");
}
}
- 定义任务的堆栈
static StackType_t LED_Task_Stack[128];
- 定义任务控制块,任务控制块就是一个结构体,里面有非常多的成员,这些成员共同描述了任务的全部信息
static StaticTask_t LED_Task_TCB;
- 定义硬件初始化函数
stm32中断优先级分组为4,即4位都用来表示抢占优先级,范围为0~15,优先级只需要分组一次即可,以后如果有其他的任务需要用到中断时,都统一用这个优先级分组,不能再分组。
static void BSP_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
LED_GPIO_Config();
USART_Config();
}
- 利用静态任务创建函数 xTaskCreateStatic()或动态任务创建函数xTaskCreate()来创建用户任务(这里是动态创建一个任务),创建任务一定要在临界区中创建,即不能被中断打断。临界段被打断的两种情况:一个是系统调度,还有一个就是外部中断。系统调度,最终也是产生Pendsv中断,在PendSV handler里面实现任务的切换。因此,FreeRTos对临界段的保护最终还是回到对中断的开和关的控制。
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;
taskENTER_CRITICAL();
xReturn = xTaskCreate((TaskFunction_t )LED_Task,
(const char* )"LED_Task",
(uint16_t )512,
(void* )NULL,
(UBaseType_t )2,
(TaskHandle_t* )&LED_Task_Handle);
if(pdPASS == xReturn)
printf("LED_Task任务创建成功!\n");
else
printf("LED_Task任务创建失败!\n");
vTaskDelete(AppTaskCreate_Handle);
taskEXIT_CRITICAL();
}
- 在main函数中创建AppTaskCreate任务,任务创建完后要开启任务调度器,这样任务才会开始运行。因为当一个任务创建好后,是处于任务就绪(Ready),在就绪态的任务可以参与操作系统的调度。
int main(void)
{
BaseType_t xReturn = pdPASS;
BSP_Init();
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,
(const char* )"AppTaskCreate",
(uint16_t )512,
(void* )NULL,
(UBaseType_t )1,
(TaskHandle_t* )&AppTaskCreate_Handle);
if(pdPASS == xReturn)
vTaskStartScheduler();
else
return -1;
while(1);
}
整个main.c文件全貌
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_led.h"
#include "bsp_usart.h"
static TaskHandle_t AppTaskCreate_Handle = NULL;
static TaskHandle_t LED_Task_Handle = NULL;
static void AppTaskCreate(void);
static void LED_Task(void* pvParameters);
static void BSP_Init(void);
int main(void)
{
BaseType_t xReturn = pdPASS;
BSP_Init();
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,
(const char* )"AppTaskCreate",
(uint16_t )512,
(void* )NULL,
(UBaseType_t )1,
(TaskHandle_t* )&AppTaskCreate_Handle);
if(pdPASS == xReturn)
vTaskStartScheduler();
else
return -1;
while(1);
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;
taskENTER_CRITICAL();
xReturn = xTaskCreate((TaskFunction_t )LED_Task,
(const char* )"LED_Task",
(uint16_t )512,
(void* )NULL,
(UBaseType_t )2,
(TaskHandle_t* )&LED_Task_Handle);
if(pdPASS == xReturn)
printf("LED_Task任务创建成功!\n");
else
printf("LED_Task任务创建失败!\n");
vTaskDelete(AppTaskCreate_Handle);
taskEXIT_CRITICAL();
}
static void LED_Task(void* parameter)
{
while(1)
{
LED1_ON;
vTaskDelay(500);
printf("LED_Task Running,LED1_ON\r\n");
LED1_OFF;
vTaskDelay(500);
printf("LED_Task Running,LED1_OFF\r\n");
}
}
static void BSP_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
LED_GPIO_Config();
USART_Config();
}
五. 静态创建和动态创建的区别
任务使用的栈和任务控制块是预先定义好的全局变量。
静态创建任务函数
static TaskHandle_t xxx_Task_Handle;
static StackType_t xxx_Task_Stack[num];
static StaticTask_t xxx_Task_TCB;
configSUPPORT_STATIC_ALLOCATION == 1
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
创建任务—SRAM 动态内存,任务使用的栈和任务控制块是在创建任务的时候FreeRTOS 动态分配的,一般我们用动态创建。
动态创建任务函数
static TaskHandle_t xxx_Task_Handle = NULL;
configSUPPORT_DYNAMIC_ALLOCATION == 1
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
|