• 嵌入式FreeRTOS学习九,任务链表的构成,TICK时间中断和任务状态切换调度


    一. tskTaskControlBlock  函数结构体

    在tskTaskControlBlock 任务控制块结构体中,其中有任务状态链表和事件链表两个链表成员,首先介绍任务状态链表这个结构,这个链表通常用于管理不同状态的任务;通常,操作系统任务有4种状态,就绪态ready,运行态running,阻塞态blocked和暂停态suspend,这些状态在任务状态链表里是怎么被管理的呢?

    1. typedef struct tskTaskControlBlock
    2. {
    3. // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
    4. volatile StackType_t *pxTopOfStack;
    5. // 表示任务状态,不同的状态会挂接在不同的状态链表下
    6. ListItem_t xStateListItem;
    7. // 事件链表项,会挂接到不同事件链表下
    8. ListItem_t xEventListItem;
    9. // 任务优先级,数值越大优先级越高
    10. UBaseType_t uxPriority;
    11. // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
    12. StackType_t *pxStack;
    13. // 任务名
    14. char pcTaskName[ configMAX_TASK_NAME_LEN ];
    15. } tskTCB;
    16. typedef tskTCB TCB_t;

    怎么管理处于不同状态的任务?
    例如:怎么取出要运行的任务?

    • 找到最高优先级的运行态、就绪态任务,运行它
    • 如果大家平级,轮流执行:排队,链表前面的先运行,运行1个tick后再去链表后面排队

    =========================================================================

    二.调用xTaskCreate函数后的执行过程

    为了更好地探讨这个问题,我们得回归FreeRTOS源码中查看tack.c文件,分析创建一个任务的时候,不同状态的任务会被放入到哪个任务状态链表里面?任务状态链表是怎么管理不同状态的任务的?分析源码可以很清晰地看到处理思路!

    • 在task.c文件中,可以看到在调用xTaskCreate函数后,系统随后的工作为TCB结构体分配内存空间,将pxStack指针指向分配的内存空间的起始地址。

    • 首先对任务结构体TCB进行了判断,创建成功后进入条件中,对任务进行prvInitialiseNewTask初始化以后,将新的任务放入到prvAddNewTaskToReadyList( pxNewTCB )就绪链表之中,再调用其它函数。

    • 那么是怎么把任务放入就绪链表的呢?就绪链表的结构又是怎样的?它是怎么管理不同任务状态的任务呢?之前在创建任务函数中调用了prvAddNewTaskToReadyList( pxNewTCB )函数接口后;进入prvAddNewTaskToReadyList( TCB_t * pxNewTCB )这个函数中。

    • prvAddNewTaskToReadyList( TCB_t * pxNewTCB )这个函数中,这个函数实际上是调用了 prvAddTaskToReadyList( pxTCB ) 函数

    • 跳转到prvAddTaskToReadyList( pxTCB ) 函数,从这里可以看出,这里将任务插入到链表的尾部,pxReadyTasksLists是一个由多个链表数组构成的结构体,里面包含了很多不同优先级的链表结构,具体是插入哪一个链表,由任务的优先级uxPriority来决定。 

     =========================================================================

    三.链表的组成结构

    任务在就绪态时的切换

    在FreeRTOS操作系统的源码中,可以看到有5种类型的链表,这里我们只用到三种类型,这三种分别为pxReadyTasksLists就绪链表,xDelayedTaskList1/xDelayedTaskList2阻塞链表, xPendingReadyList休眠链表;  其中延时链表和休眠链表这两类只有一个链表。就绪链表的种类则和优先级的大小有关,也可以由代码宏控制设定。

    1. PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
    2. PRIVILEGED_DATA static List_t xDelayedTaskList1;
    3. PRIVILEGED_DATA static List_t xDelayedTaskList2;
    4. PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
    5. PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
    6. PRIVILEGED_DATA static List_t xPendingReadyList;

    上述的pxReadyTasksLists链表的有5个优先级,链表的结构为

    1. //在task.c文件中
    2. PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
    3. //在FreeRTOS.h文件中
    4. #define configMAX_PRIORITIES ( 5 )

    可以看出pxReadyTasksLists链表有5个优先级,因此该链表是由5个链表结构组成的,在例子中,我们创建了3个任务函数,这三个任务分别根据不同的优先级进入不同的任务就绪链表中。

    1. int main( void )
    2. {
    3. prvSetupHardware();
    4. xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
    5. xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
    6. xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
    7. /* 启动调度器 */
    8. vTaskStartScheduler();
    9. /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    10. return 0;
    11. }

    FreeRTOS操作系统中,优先级为3的Task 3函数进入pxReadyTasksList[2]的链表中,优先级为0的Task 2函数进入pxReadyTasksList[0]的链表中,优先级为0的Task 1函数进入pxReadyTasksList[0]的链表中,任务在处于在同一状态下的情况下,系统会按照从大到小的顺序遍历这些链表,即优先级的顺序,链表中有任务则将任务拿出来执行。

    =========================================================================四.任务的调度问题

    FreeRTOS操作系统中,由TICK中断执行任务的调度,所谓TICK中断,就是每隔固定时间,系统会产生的定时器中断,可以由代码设定时间轴,每隔设定的固定时间产生一个TICK中断。FreeRTOS操作系统的间隔配置通常是1ms。产生中断后,就会去调用TICK中断函数。

    对于FreeRTOS操作系统,TICK中断比较简单,每个任务只能执行一个TICK。因为Task 2最后插入链表,所以Task 1先执行,Task 1执行1个tick后,Task 1会中断,马上切换到Task 2执行。系统会保存当前任务Task 1的寄存器现场,然后执行新任务。Task 2执行一个TICK后,也是如此,保存Task 2的寄存器现场,然后寻找新的任务执行。

    注意看下面的任务 vTask3的任务函数,里面加了vTaskDelay( xDelay5ms )延时函数,因为vTask3是最高优先级任务,如果不加延时,系统会一直执行vTask3,其它的任务完全不能抢占和执行,vTask1和vTask2根本没有机会执行,不会打印任何信息。

    1. void vTask1( void *pvParameters )
    2. {
    3. /* 任务函数的主体一般都是无限循环 */
    4. for( ;; )
    5. {
    6. flagIdleTaskrun = 0;
    7. flagTask1run = 1;
    8. flagTask2run = 0;
    9. flagTask3run = 0;
    10. /* 打印任务的信息 */
    11. printf("T1\r\n");
    12. }
    13. }
    14. void vTask2( void *pvParameters )
    15. {
    16. /* 任务函数的主体一般都是无限循环 */
    17. for( ;; )
    18. {
    19. flagIdleTaskrun = 0;
    20. flagTask1run = 0;
    21. flagTask2run = 1;
    22. flagTask3run = 0;
    23. /* 打印任务的信息 */
    24. printf("T2\r\n");
    25. }
    26. }
    27. void vTask3( void *pvParameters )
    28. {
    29. const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
    30. /* 任务函数的主体一般都是无限循环 */
    31. for( ;; )
    32. {
    33. flagIdleTaskrun = 0;
    34. flagTask1run = 0;
    35. flagTask2run = 0;
    36. flagTask3run = 1;
    37. /* 打印任务的信息 */
    38. printf("T3\r\n");
    39. // 如果不休眠的话, 其他任务无法得到执行
    40. vTaskDelay( xDelay5ms );
    41. }
    42. }

    执行完vTaskDelay( xDelay5ms )这个函数后,vTask3这个任务就会从任务就绪状态改变为任务阻塞状态,也就是从就绪链表被移到阻塞链表。然后系统再从任务就绪链表里面寻找可以执行的任务,任务vTask1和vTask2有机会得到执行。vTaskDelay( xDelay5ms )实际上就是改变任务的状态。vTaskDelay( xDelay5ms )设置了5个TICK时间,系统每隔一个TICK时间就会检查vTask3是否可以恢复运行,经过5个TICK时间后,在7的位置,恢复vTask3寄存器现场,vTask3再次运行。

     

  • 相关阅读:
    Vue2项目练手——通用后台管理项目第六节
    1058 选择题 (利用ASCII码)
    零基础重庆自考本科行政管理难吗?
    Node-Web模块的用法
    Java学习—Collections工具类
    使用vue-cli搭建SPA项目
    「JVS低代码开发平台」关于逻辑引擎的触发讲解
    Java-堆和栈的概念和区别-1
    Jenkins发送邮件(简洁版)
    7.Python_结构型模式_桥模式
  • 原文地址:https://blog.csdn.net/weixin_44651073/article/details/127868026