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

一. tskTaskControlBlock  函数结构体

在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;

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

  • 找到最高优先级的运行态、就绪态任务,运行它
  • 如果大家平级,轮流执行:排队,链表前面的先运行,运行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休眠链表;  其中延时链表和休眠链表这两类只有一个链表。就绪链表的种类则和优先级的大小有关,也可以由代码宏控制设定。

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再次运行。

 

猜你喜欢

转载自blog.csdn.net/weixin_44651073/article/details/127868026
今日推荐