1、什么是时间片调度
在任务优先级相同的时候,CPU会轮流使用相同的时间去执行它,即时间片调度。这个相同的时间就是时间片。而时间片的大小就是SysTick的中断周期(SysTick的中断周期可以修改)。
比如有三个相同优先级的任务在运行,时间片大小为10ms。那么CPU前10ms执行task1,然后用10ms执行task2,再花10ms执行task3,再回到task1执行10ms,这样的轮流执行被称之为时间片流转。如果期间有一个任务执行到一半被挂载了,那么CPU就会立马抛弃这个任务执行下一个任务。
2、时间片调度实验
代码内容为:将时间片设置为50ms,然后创建task1和task2来不断在串口打印他们的运行次数。
步骤1:
要想使用时间片必须先将这两个宏置1,这两个宏在 FreeRTOSConfig.h 中;
#define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程
#define configUSE_TIME_SLICING 1 //1使能时间片调度(默认式使能的)
步骤2:
在 FreeRTOSConfig.h 中找到此宏,修改1000为20;因为1000hz对应的 SysTick 中断周期即时间片大小是1ms。我们要改为50ms,就应该将1000hz / 50 = 20hz;
#define configTICK_RATE_HZ (20) //时钟节拍频率,这里设置为1000,周期就是1ms
步骤3:
创建任务:
需要注意的是:
1、我们使用的是delay_ms(10)而不是vTaskDelay(10),因为后者会挂载当前任务转而执行另一个任务,这样就起不到50ms流转的效果了。而10ms的延时会让CPU停止工作。所以我们每50ms进行一次任务切换,然后每执行一次任务会延时10ms,所以每次任务中按理应该执行5遍while循环。又因为 printf 比较耗时,再加上其他代码的执行,实际每次任务while循环会执行4~5次。
2、因为实际每次任务while循环会执行4~5次,所以有时候在串口打印数据打印一半就可能会产生任务切换执行另一个任务,然后导致串口打印出现问题。所以我们在每次串口打印的时候加上临界段保护。
/*START_TASK任务配置*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/*TASK1任务配置*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/*TASK2任务配置*/
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128 //128字=128*4字节
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/*入口函数*/
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task, //任务函数
(char * ) "start_task", //任务名称
(uint16_t ) START_TASK_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) START_TASK_PRIO, //任务优先级
(TaskHandle_t * ) &start_task_handler ); //任务句柄
vTaskStartScheduler(); //开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); //进入临界区
printf("start_task正在运行\r\n");
xTaskCreate( (TaskFunction_t ) task1, //任务函数
(char * ) "task1", //任务名称
(uint16_t ) TASK1_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) TASK1_PRIO, //任务优先级
(TaskHandle_t * ) &task1_handler ); //任务句柄
xTaskCreate( (TaskFunction_t ) task2, //任务函数
(char * ) "task2", //任务名称
(uint16_t ) TASK2_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数
(UBaseType_t ) TASK2_PRIO, //任务优先级
(TaskHandle_t * ) &task2_handler ); //任务句柄
vTaskDelete(NULL); //vTaskDelete(start_task_handler);
taskEXIT_CRITICAL(); //退出临界区
}
void task1( void * pvParameters )
{
uint32_t task1_num=0;
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("task1的运行次数 = %d\r\n",++task1_num);
taskEXIT_CRITICAL(); //退出临界区
delay_ms(10);
}
}
void task2( void * pvParameters )
{
uint32_t task2_num=0;
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("task2的运行次数 = %d\r\n",++task2_num);
taskEXIT_CRITICAL(); //退出临界区
delay_ms(10);
}
}