FreeRTOS学习11——时间片任务调度
时间片任务调度
- 时间片任务调度
时间片任务调度
概念:时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运行的时间就是一个系统时钟节拍。
同等优先级任务轮流地享有相同的 CPU 时间(可设置), 叫时间片,在FreeRTOS中,一个时间片就等于SysTick 中断周期
特点:
- 同等优先级任务,轮流执行;时间片流转
- 一个时间片大小,取决为滴答定时器中断频率
- 注意没有用完的时间片不会再使用,下个任务得到执行还是按照一个时间片的时钟节拍运行
下面我们举个例子详细说
我们创建优先级相同的几个任务,其运行流程如下
- 首先Task1运行完一个时间片后,切换至Task2运行
- Task2运行完一个时间片后,切换至Task3运行
- Task3运行过程中(还不到一个时间片),Task3阻塞了(系统延时或等待信号量等),此时直接切换到下一个任务Task1
- Task1运行完一个时间片后,切换至Task2运行
接下来用一个实验来验证我们的理论
实验设计:将设计三个任务:start_task、task1、task2,其中task1和task2优先级相同均为2。为了使现象明显,将滴答定时器的中断频率设置为50ms中断一次,即一个时间片50ms
三个任务的功能如下:
- start_task:用来创建其他的2个任务
- task1:通过串口打印task1的运行次数
- task2:通过串口打印task2的运行次数
注意:使用时间片调度需把宏 configUSE_TIME_SLICING 和configUSE_PREEMPTION 置1
在打印任务运行次数的时候需要使用到串口硬件,为了避免多个任务“同时”使用同一个硬件,因此在使用串口硬件打印任务运行次数之前,进入临界区,在使用串口硬件打印任务运行次数之后,再退出临界区
实验代码freertos_demo.c
#include "freertos_demo.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "delay.h"
/*FreeRTOS********************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/*****************************************************************************/
/*FreeRTOS 配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1 /* 任务优先级 */
#define START_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler; /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2 /* 任务优先级 */
#define TASK1_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 2 /* 任务优先级 */
#define TASK2_STK_SIZE 128 /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */
/*****************************************************************************/
/**
* @brief FreeRTOS 例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler(); /* 开启任务调度 */
}
/**
* @brief start_task
* @param pvParameters : 无
* @retval 无
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 在临界区内不进行任务调度,保证新创建的任务不会抢占start_task,也就是等所有任务创建完后再开始任务调度*/
/* 创建任务 1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务 2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task1",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(NULL); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/**
* @brief task1
* @param pvParameters : 无
* @retval 无
*/
/* task1实现LED0每200ms闪烁一次*/
void task1(void *pvParameters)
{
static uint16_t num;
while(1)
{
taskENTER_CRITICAL();
num++;
printf("任务1运行次数:%d\r\n",num);
taskEXIT_CRITICAL();
delay_ms(10);
}
}
/* task2 列表项的插入和删除*/
void task2(void *pvParameters)
{
static uint16_t num;
while(1)
{
taskENTER_CRITICAL();
num++;
printf("任务2运行次数:%d\r\n",num);
taskEXIT_CRITICAL();
delay_ms(10);
}
}
实验现象
每个任务执行 四到五次
与理论相符
delay_ms(10);延时为 “死延时” 在延时时不会进行任务调度
vTaskDelay(1000);该函数在延时时会进行任务调度