找回密码
 立即注册
首页 业界区 安全 FreeRTOS 简单内核实现8 时间片轮询

FreeRTOS 简单内核实现8 时间片轮询

袁曼妮 2025-6-8 13:21:49
0、思考与回答

0.1、思考一

为什么要增加时间片轮询?
目前的 RTOS 内核已经支持抢占优先级,即高优先级的任务会抢占低优先级的任务得到执行,但是对于同等优先级的任务,如果不支持时间片轮询,则只能有一个任务运行,并且由于优先级相同所以除延时阻塞到期外也不会发生任务调度,因此需要增加时间片轮询保证同等优先级的任务能得到轮流执行
1、内核程序修改

1.1、xTaskIncrementTick( )

在该函数中除了任务延时阻塞时间到期产生任务调度外,增加支持时间片轮询的任务切换,具体如下所示
  1. /*task.c*/
  2. BaseType_t xTaskIncrementTick(void)
  3. {
  4.         // 省略未修改的程序
  5.         ......
  6. #if((configUSE_PREEMPTION == 1) && (configUSE_TIME_SLICING == 1))
  7.         // 支持时间片轮询
  8.         if(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[pxCurrentTCB->uxPriority])) > 1)
  9.         {
  10.                 xSwitchRequired = pdTRUE;
  11.         }
  12. #endif
  13.         return xSwitchRequired;
  14. }
复制代码
  1. /* FreeRTOSConfig.h */
  2. // 支持时间片轮询
  3. #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、测试" 小节内容,将两个任务的优先级修改为一样,然后在两个任务中均使用软件延时模拟任务连续运行,具体程序如下所示
  1. /* main.c */
  2. /* USER CODE BEGIN Includes */
  3. #include "FreeRTOS.h"
  4. /* USER CODE END Includes */
  5. /* USER CODE BEGIN PV */
  6. // 软件延时
  7. void delay(uint32_t count)
  8. {
  9.         for(;count!=0;count--);
  10. }
  11. TaskHandle_t Task1_Handle;
  12. #define TASK1_STACK_SIZE                    128
  13. StackType_t Task1Stack[TASK1_STACK_SIZE];
  14. TCB_t Task1TCB;
  15. UBaseType_t Task1Priority = 2;
  16. TaskHandle_t Task2_Handle;
  17. #define TASK2_STACK_SIZE                    128
  18. StackType_t Task2Stack[TASK2_STACK_SIZE];
  19. TCB_t Task2TCB;
  20. UBaseType_t Task2Priority = 2;
  21. // 任务 1 入口函数
  22. void Task1_Entry(void *parg)
  23. {
  24.         for(;;)
  25.         {
  26.                 HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
  27.                 delay(10000000);
  28.         }
  29. }
  30. // 任务 2 入口函数
  31. void Task2_Entry(void *parg)
  32. {
  33.         for(;;)
  34.         {
  35.                 HAL_GPIO_TogglePin(ORANGE_LED_GPIO_Port, ORANGE_LED_Pin);
  36.                 delay(10000000);
  37.         }
  38. }
  39. /* USER CODE END PV */
  40. /* USER CODE BEGIN 2 */
  41. // 创建任务 1 和 2
  42. Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,
  43.                                                                  (char *)"Task1",
  44.                                                                  (uint32_t)TASK1_STACK_SIZE,
  45.                                                                  (void *)NULL,
  46.                                                                  (UBaseType_t)Task1Priority,
  47.                                                                  (StackType_t *)Task1Stack,
  48.                                                                  (TCB_t *)&Task1TCB);
  49.                                                                                                                
  50. Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry,
  51.                                                                  (char *)"Task2",
  52.                                                                  (uint32_t)TASK2_STACK_SIZE,
  53.                                                                  (void *) NULL,
  54.                                                                  (UBaseType_t)Task2Priority,
  55.                                                                  (StackType_t *)Task2Stack,
  56.                                                                  (TCB_t *)&Task2TCB );
  57. // 启动任务调度器,永不返回
  58. vTaskStartScheduler();
  59. /* USER CODE END 2 */
复制代码
将 configUSE_TIME_SLICING 调整为 0 ,然后烧录程序,仍然使用逻辑分析仪捕获两个 LED 的引脚电平,结果如下图所示
1.png

可以发现在不启用时间片轮询时,由于两个任务优先级一致,并且两个任务模拟连续运行,因此只有任务 Task2 被运行 (为什么是 Task2 ?)
将 configUSE_TIME_SLICING 调整为 1,然后烧录程序,仍然使用逻辑分析仪捕获两个 LED 的引脚电平,结果如下图所示
2.png

可以发现,对于优先级相同且连续运行的任务几乎是在同时运行,其实是因为每个时间片(滴答定时器间隔)都发生了一次任务调度
3.2、待改进

当前 RTOS 简单内核已实现的功能有

  • 静态方式创建任务
  • 手动切换任务
  • 临界段保护
  • 任务阻塞延时
  • 支持任务优先级
  • 阻塞链表
  • 时间片轮询
后续 RTOS 简单内核可以增加

  • 任务间通信机制

    • 信号量
    • 互斥锁
    • 消息队列
    • ......

  • 其他功能

    • 软件定时器
    • ......


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册