0、思考与回答
0.1、思考一
为什么要增加时间片轮询?
目前的 RTOS 内核已经支持抢占优先级,即高优先级的任务会抢占低优先级的任务得到执行,但是对于同等优先级的任务,如果不支持时间片轮询,则只能有一个任务运行,并且由于优先级相同所以除延时阻塞到期外也不会发生任务调度,因此需要增加时间片轮询保证同等优先级的任务能得到轮流执行
1、内核程序修改
1.1、xTaskIncrementTick( )
在该函数中除了任务延时阻塞时间到期产生任务调度外,增加支持时间片轮询的任务切换,具体如下所示
/*task.c*/
BaseType_t xTaskIncrementTick(void)
{
// 省略未修改的程序
......
#if((configUSE_PREEMPTION == 1) && (configUSE_TIME_SLICING == 1))
// 支持时间片轮询
if(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[pxCurrentTCB->uxPriority])) > 1)
{
xSwitchRequired = pdTRUE;
}
#endif
return xSwitchRequired;
}
/* FreeRTOSConfig.h */
// 支持时间片轮询
#define configUSE_TIME_SLICING 1
1.2、原理
假设当前系统中只存在两个优先级相同的任务,时间片轮询任务切换流程如下
xTaskIncrementTick()
-> vTaskSwitchContext()
-> taskSELECT_HIGHEST_PRIORITY_TASK()
-> listGET_OWNER_OF_NEXT_ENTRY()
当进入滴答定时器中断服务函数时,如果发现就绪链表数组中的某个链表中链表项的数量大于 1 ,则表示该优先级下有不止一个任务,此时就可以产生任务调度
当有任务调度产生的时候,会调用 vTaskSwitchContext()
和 taskSELECT_HIGHEST_PRIORITY_TASK()
两个函数寻找当前的最高优先级任务,但是系统中只有两个优先级相同的任务,因此最高优先级仍然没变,但是在这个优先级下返回的任务却变成了下一个
为什么呢?
关键在于 listGET_OWNER_OF_NEXT_ENTRY()
函数,这个宏函数每次调用会获取链表中下一个链表项的 pvOwner
参数,由于是双向链表,因此会不断的循环链表中的链表项(两个同等优先级的任务),每次发生任务调度就会切换一次任务,所以就实现了时间片轮询
3、实验
3.1、测试
参考 FreeRTOS 简单内核实现6 优先级 "3.1、测试" 小节内容,将两个任务的优先级修改为一样,然后在两个任务中均使用软件延时模拟任务连续运行,具体程序如下所示
/* main.c */
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
// 软件延时
void delay(uint32_t count)
{
for(;count!=0;count--);
}
TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;
UBaseType_t Task1Priority = 2;
TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;
UBaseType_t Task2Priority = 2;
// 任务 1 入口函数
void Task1_Entry(void *parg)
{
for(;;)
{
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
delay(10000000);
}
}
// 任务 2 入口函数
void Task2_Entry(void *parg)
{
for(;;)
{
HAL_GPIO_TogglePin(ORANGE_LED_GPIO_Port, ORANGE_LED_Pin);
delay(10000000);
}
}
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// 创建任务 1 和 2
Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,
(char *)"Task1",
(uint32_t)TASK1_STACK_SIZE,
(void *)NULL,
(UBaseType_t)Task1Priority,
(StackType_t *)Task1Stack,
(TCB_t *)&Task1TCB);
Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry,
(char *)"Task2",
(uint32_t)TASK2_STACK_SIZE,
(void *) NULL,
(UBaseType_t)Task2Priority,
(StackType_t *)Task2Stack,
(TCB_t *)&Task2TCB );
// 启动任务调度器,永不返回
vTaskStartScheduler();
/* USER CODE END 2 */
将 configUSE_TIME_SLICING
调整为 0 ,然后烧录程序,仍然使用逻辑分析仪捕获两个 LED 的引脚电平,结果如下图所示
可以发现在不启用时间片轮询时,由于两个任务优先级一致,并且两个任务模拟连续运行,因此只有任务 Task2 被运行 (为什么是 Task2 ?)
将 configUSE_TIME_SLICING
调整为 1,然后烧录程序,仍然使用逻辑分析仪捕获两个 LED 的引脚电平,结果如下图所示
可以发现,对于优先级相同且连续运行的任务几乎是在同时运行,其实是因为每个时间片(滴答定时器间隔)都发生了一次任务调度
3.2、待改进
当前 RTOS 简单内核已实现的功能有
- 静态方式创建任务
- 手动切换任务
- 临界段保护
- 任务阻塞延时
- 支持任务优先级
- 阻塞链表
- 时间片轮询
后续 RTOS 简单内核可以增加
- 任务间通信机制
- 信号量
- 互斥锁
- 消息队列
- ......
- 其他功能
- 软件定时器
- ......