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 简单内核可以增加
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |