在tskTaskControlBlock 任务控制块结构体中,其中有任务状态链表和事件链表两个链表成员,首先介绍任务状态链表这个结构,这个链表通常用于管理不同状态的任务;通常,操作系统任务有4种状态,就绪态ready,运行态running,阻塞态blocked和暂停态suspend,这些状态在任务状态链表里是怎么被管理的呢?
- typedef struct tskTaskControlBlock
- {
- // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
- volatile StackType_t *pxTopOfStack;
- // 表示任务状态,不同的状态会挂接在不同的状态链表下
- ListItem_t xStateListItem;
- // 事件链表项,会挂接到不同事件链表下
- ListItem_t xEventListItem;
- // 任务优先级,数值越大优先级越高
- UBaseType_t uxPriority;
- // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
- StackType_t *pxStack;
- // 任务名
- char pcTaskName[ configMAX_TASK_NAME_LEN ];
- } tskTCB;
- typedef tskTCB TCB_t;
怎么管理处于不同状态的任务?
例如:怎么取出要运行的任务?
=========================================================================
为了更好地探讨这个问题,我们得回归FreeRTOS源码中查看tack.c文件,分析创建一个任务的时候,不同状态的任务会被放入到哪个任务状态链表里面?任务状态链表是怎么管理不同状态的任务的?分析源码可以很清晰地看到处理思路!





=========================================================================
任务在就绪态时的切换
在FreeRTOS操作系统的源码中,可以看到有5种类型的链表,这里我们只用到三种类型,这三种分别为pxReadyTasksLists就绪链表,xDelayedTaskList1/xDelayedTaskList2阻塞链表, xPendingReadyList休眠链表; 其中延时链表和休眠链表这两类只有一个链表。就绪链表的种类则和优先级的大小有关,也可以由代码宏控制设定。
- PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
- PRIVILEGED_DATA static List_t xDelayedTaskList1;
- PRIVILEGED_DATA static List_t xDelayedTaskList2;
- PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
- PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
- PRIVILEGED_DATA static List_t xPendingReadyList;
上述的pxReadyTasksLists链表的有5个优先级,链表的结构为
- //在task.c文件中
- PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
- //在FreeRTOS.h文件中
- #define configMAX_PRIORITIES ( 5 )
可以看出pxReadyTasksLists链表有5个优先级,因此该链表是由5个链表结构组成的,在例子中,我们创建了3个任务函数,这三个任务分别根据不同的优先级进入不同的任务就绪链表中。
- int main( void )
- {
- prvSetupHardware();
- xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
- xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
- xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
- /* 启动调度器 */
- vTaskStartScheduler();
- /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
- return 0;
- }
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根本没有机会执行,不会打印任何信息。
- void vTask1( void *pvParameters )
- {
- /* 任务函数的主体一般都是无限循环 */
- for( ;; )
- {
- flagIdleTaskrun = 0;
- flagTask1run = 1;
- flagTask2run = 0;
- flagTask3run = 0;
-
- /* 打印任务的信息 */
- printf("T1\r\n");
- }
- }
-
- void vTask2( void *pvParameters )
- {
- /* 任务函数的主体一般都是无限循环 */
- for( ;; )
- {
- flagIdleTaskrun = 0;
- flagTask1run = 0;
- flagTask2run = 1;
- flagTask3run = 0;
-
- /* 打印任务的信息 */
- printf("T2\r\n");
- }
- }
- void vTask3( void *pvParameters )
- {
- const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
-
- /* 任务函数的主体一般都是无限循环 */
- for( ;; )
- {
- flagIdleTaskrun = 0;
- flagTask1run = 0;
- flagTask2run = 0;
- flagTask3run = 1;
- /* 打印任务的信息 */
- printf("T3\r\n");
- // 如果不休眠的话, 其他任务无法得到执行
- vTaskDelay( xDelay5ms );
- }
- }
执行完vTaskDelay( xDelay5ms )这个函数后,vTask3这个任务就会从任务就绪状态改变为任务阻塞状态,也就是从就绪链表被移到阻塞链表。然后系统再从任务就绪链表里面寻找可以执行的任务,任务vTask1和vTask2有机会得到执行。vTaskDelay( xDelay5ms )实际上就是改变任务的状态。vTaskDelay( xDelay5ms )设置了5个TICK时间,系统每隔一个TICK时间就会检查vTask3是否可以恢复运行,经过5个TICK时间后,在7的位置,恢复vTask3寄存器现场,vTask3再次运行。
