栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【FreeRTOS学习计划】 第八节 任务延时列表的实现

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【FreeRTOS学习计划】 第八节 任务延时列表的实现

在本节之前,为了实现任务的阻塞延时,在任务控制块中内置了一个延时变量xTicksToDelay。每当任务需要延时的时候,就初始化 xTicksToDelay 需要延时的时间,然后将任务挂起,这里的挂起只是将任务在优先级位图表 uxTopReadyPriority 中对应的位清零,并不会将任务从就绪列表中删除。当每次时基中断(SysTick 中断)来临时,就扫描就绪列表中的每个任务的 xTicksToDelay,如果 xTicksToDelay 大于 0 则递减一次,然后判断 xTicksToDelay 是否为 0,如果为 0 则表示延时时间到,将该任务就绪(即将任务在优先级位图表 uxTopReadyPriority 中对应的位置位),然后进行任务切换。这种延时的缺点是,在每个时基中断中需要对所有任务都扫描一遍,费时,优点是容易理解。之所以先这样讲解是为了慢慢地过度到 FreeRTOS 任务延时列表的讲解。

任务延时列表的工作原理

在 FreeRTOS 中,有一个任务延时列表(实际上有两个,为了方便讲解原理,我们假装合并为一个,其实两个的作用是一样的),当任务需要延时的时候,则先将任务挂起,即先将任务从就绪列表删除,然后插入到任务延时列表,同时更新下一个任务的解锁时刻变量:xNextTaskUnblockTime的值。

xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时的值 xTicksToDelay。当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪。与 RT-Thread 和 μC/OS 在解锁延时任务时要扫描定时器列表这种时间不确定性的方法相比,FreeRTOS 这个xNextTaskUnblockTime 全局变量设计的非常巧妙。

实现任务延时列表 定义任务延时列表

任务延时列表在 task.c 中定义

static List_t xDelayedTaskList1;//①
static List_t xDelayedTaskList2;//②
static List_t * volatile pxDelayedTaskList;//③
static List_t * volatile pxOverflowDelayedTaskList;//④

①②FreeRTOS定义了两个任务延时列表,当系统时基计数器xTickCount没有溢出时,用一条列表,当 xTickCount 溢出后,用另外一条列表。

③任务延时列表指针,指向 xTickCount 没有溢出时使用的那条列表。

④任务延时列表指针,指向 xTickCount 溢出时使用的那条列表。

任务延时列表初始化

任务延时列表属于任务列表的一种,在 prvInitialiseTaskLists() 函数中初始化

void prvInitialiseTaskLists( void )
{
  UbaseType_t uxPriority;
	
	for( uxPriority = ( UbaseType_t ) 0U; uxPriority < ( UbaseType_t ) configMAX_PRIORITIES; uxPriority++)
	{
	  vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
	}
	vListInitialise( &xDelayedTaskList1 );
	vListInitialise( &xDelayedTaskList2 );
    
	pxDelayedTaskList = &xDelayedTaskList1;
	pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

定义 xNextTaskUnblockTime

xNextTaskUnblockTime 是一个在 task.c 中定义的静态变量,用于表示下一个任务的解锁时刻。xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时值 xTicksToDelay。当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪。

初始化 xNextTaskUnblockTime

xNextTaskUnblockTime 在 vTaskStartScheduler() 函 数 中 初 始 化 为 portMAX_DELAY(portMAX_DELAY 是一个 portmacro.h 中定义的宏,默认为 0xffffffffUL)。

#if( configUSE_16_BIT_TICKS == 1)
    typedef uint16_t TickType_t;
    #define portMAX_DELAY ( TickType_t ) 0xffff
#else
    typedef uint32_t TickType_t;
    #define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif

初始化 xNextTaskUnblockTime

void vTaskStartScheduler( void )
{
     
    TCB_t *pxIdleTaskTCBBuffer = NULL;               
    StackType_t *pxIdleTaskStackBuffer = NULL;       
    uint32_t ulIdleTaskStackSize;
    
    
    vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, 
                                   &pxIdleTaskStackBuffer, 
                                   &ulIdleTaskStackSize );    
    
    xIdleTaskHandle = xTaskCreateStatic(	(TaskFunction_t)prvIdleTask,              
																					(char *)"IDLE",                           
																					(uint32_t)ulIdleTaskStackSize ,           
																					(void *) NULL,                            
																					(UbaseType_t) tskIDLE_PRIORITY,           
																					(StackType_t *)pxIdleTaskStackBuffer,     
																					(TCB_t *)pxIdleTaskTCBBuffer );           					

		
//                                     
//    vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
//
//                                         
//    
//    pxCurrentTCB = &Task1TCB;
    xNextTaskUnblockTime = portMAX_DELAY;                                    
    
    xTickCount = ( TickType_t ) 0U;
    
    
    if( xPortStartScheduler() != pdFALSE )
    {
        
    }
}
修改代码,支持任务延时列表 修改 vTaskDelay() 函数
void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    
    pxTCB = pxCurrentTCB;
    
    
    //pxTCB->xTicksToDelay = xTicksToDelay;
	
	
    prvAddCurrentTaskToDelayedList( xTicksToDelay );
    
    
    taskYIELD();
}

①从本节开始,添加了任务的延时列表,延时的时候不用再依赖任务TCB 中内置的延时变量 xTicksToDelay。

②将任务插入到延时列表。函数prvAddCurrentTaskToDelayedList() 在task.c 中定义。

prvAddCurrentTaskToDelayedList() 函数
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait )
{
    TickType_t xTimeToWake;
    
    
    const TickType_t xConstTickCount = xTickCount;

    
	if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UbaseType_t ) 0 )
	{
		
        portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
	}

    
    xTimeToWake = xConstTickCount + xTicksToWait;

    
    listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

    
    if( xTimeToWake < xConstTickCount )
    {
        vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
    }
    else 
    {

        vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

        
        if( xTimeToWake < xNextTaskUnblockTime )
        {
            xNextTaskUnblockTime = xTimeToWake;
        }
    }	
}

①获取系统时基计数器 xTickCount 的值,xTickCount 是一个在 task.c 中定义的全局变量,用于记录 SysTick 的中断次数。

②调用函数 uxListRemove() 将任务从就绪列表移除,uxListRemove() 会返回当前链表下节点的个数,如果为 0,则表示当前列表下没有任务就绪,则调用函数portRESET_READY_PRIORITY() 将任务在优先级位图表 uxTopReadyPriority 中对应的位清除。因为 FreeRTOS 支持同一个优先级下可以有多个任务,所以在清除优先级位图表 uxTopReadyPriority 中对应的位时要判断下该优先级下的就绪列表是否还有其他的任务。目前为止,我们还没有支持同一个优先级下有多个任务的功能,这个功能我们将在下一节“支持时间片”里面实现。

③计算任务延时到期时,系统时基计数器 xTickCount 的值是多少。

④将任务延时到期的值设置为节点的排序值。将任务插入到延时列表时就是根据这个值来做升序排列的,最先延时到期的任务排在最前面。

⑤:xTimeToWake 溢出,将任务插入到溢出任务延时列表。溢出?什么意思?xTimeToWake 等于系统时基计数器 xTickCount 的值加上任务需要延时的时间 xTicksToWait。举例:如果当前 xTickCount 的值等于 0xfffffffdUL,xTicksToWait 等于 0x03,那么xTimeToWake = 0xfffffffdUL + 0x03 = 1,显然得出的值比任务需要延时的时间 0x03 还小,这肯定不正常,说明溢出了,这个时候需要将任务插入到溢出任务延时列表。

⑥xTimeToWake 没有溢出,则将任务插入到正常任务延时列表。

⑦更新下一个任务解锁时刻变量 xNextTaskUnblockTime 的值。这一步很重要,在 xTaskIncrementTick() 函数中,我们只需要让系统时基计数器 xTickCount 与xNextTaskUnblockTime 的值先比较就知道延时最快结束的任务是否到期。

修改 xTaskIncrementTick() 函数
void xTaskIncrementTick( void )
{
	TCB_t * pxTCB;
	TickType_t xItemValue;

	const TickType_t xConstTickCount = xTickCount + 1;//①
	xTickCount = xConstTickCount;

	
	if( xConstTickCount == ( TickType_t ) 0U )
	{
		taskSWITCH_DELAYED_LISTS();
	}

	
	if( xConstTickCount >= xNextTaskUnblockTime )
	{
		for( ;; )
		{
			if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )//④
			{
				
				xNextTaskUnblockTime = portMAX_DELAY;
				break;
			}
			else 
			{
				pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
				xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );//⑥

				
                if( xConstTickCount < xItemValue )
				{
					xNextTaskUnblockTime = xItemValue;
					break;
				}

				
				( void ) uxListRemove( &( pxTCB->xStateListItem ) );

				
				prvAddTaskToReadyList( pxTCB );
			}
		}
	}
    
    
    portYIELD();
}

①更新系统时基计数器 xTickCount 的值。

②如果系统时基计数器 xTickCount 溢出,则切换延时列表。taskSWITCH_DELAYED_LISTS() 函数在 task.c 中定义

taskSWITCH_DELAYED_LISTS() 函数
#define taskSWITCH_DELAYED_LISTS()
{
	List_t *pxTemp;//(1)
	pxTemp = pxDelayedTaskList;
	pxDelayedTaskList = pxOverflowDelayedTaskList;
	pxOverflowDelayedTaskList = pxTemp;
	xNumOfOverflows++;
	prvResetNextTaskUnblockTime();//②
}

(1)切换延时列表,实际就是更换pxDelayedTaskList和pxOverflowDelayedTaskList 这两个指针的指向。

(2)复位 xNextTaskUnblockTime 的值。prvResetNextTaskUnblockTime() 函数在 task.c 中定义。

prvResetNextTaskUnblockTime 函数
static void prvResetNextTaskUnblockTime( void )
{
    TCB_t *pxTCB;

	if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
	{
		
		xNextTaskUnblockTime = portMAX_DELAY;
	}
	else
	{
		
		( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
		xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
	}
}

(1)当前延时列表为空,则设置 xNextTaskUnblockTime 等于最大值。

(2)当前列表不为空,则有任务在延时,则获取当前列表下第一个节点的排序值,然后将该节点的排序值更新到xNextTaskUnblockTime。

③有任务延时到期,则进入下面的 for 循环,一一将这些延时到期的任务从延时列表移除。

④延时列表为空,则将 xNextTaskUnblockTime 设置为最大值,然后跳出 for 循环。

⑤延时列表不为空,则需要将延时列表里面延时到期的任务删除,并将它们添加到就绪列表。

⑥取出延时列表第一个节点的排序辅助值。

⑦直到将延时列表中所有延时到期的任务移除才跳出 for 循环。延时列表中有可能存在多个延时相等的任务。

⑧将任务从延时列表移除,消除等待状态。

⑨将解除等待的任务添加到就绪列表。

⑩执行一次任务切换。

修改 taskRESET_READY_PRIORITY() 函数

在没有添加任务延时列表之前,与任务相关的列表只有一个,就是就绪列表,无论任务在延时还是就绪都只能通过扫描就绪列表来找到任务的 TCB,从而实现系统调度。所以在上一节“支持多优先级”中,实现 taskRESET_READY_PRIORITY() 函数的时候,不用先判断当前优先级下就绪列表中的列表的节点是否为 0,而是直接把任务在优先级位图表uxTopReadyPriority 中对应的位清零。因为当前优先级下就绪列表中的列表的节点不可能为 0,目前我们还没有添加其他列表来存放任务的 TCB,只有一个就绪列表。

但是从本节开始,我们额外添加了延时列表,当任务要延时的时候,将任务从就绪列表移除,然后添加到延时列表,同时将任务在优先级位图表 uxTopReadyPriority 中对应的位清除。在清除任务在优先级位图表 uxTopReadyPriority 中对应的位的时候,与上一章不同的是需要判断就绪列表pxReadyTasksLists[] 在当前优先级下对应的链表的节点是否为 0,只有当该列表下没有任务时才真正地将任务在优先级位图表 uxTopReadyPriority 中对应的位清零。

taskRESET_READY_PRIORITY() 函数的具体修改见代码如下。那什么情况下就绪列表的列表里面会有多个任务节点?即同一优先级下有多个任务?这个就是我们下一节“支持时间片”要讲的内容。

#if 0 //本节实现方法
	#define taskRESET_READY_PRIORITY( uxPriority )														
	{																									
		if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UbaseType_t ) 0 )	
		{																								
			portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );							
		}																								
	}
#else //上一节实现方法
    #define taskRESET_READY_PRIORITY( uxPriority )											    
    {																							
            portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );					
    }
#endif
main函数
int main(void)
{
	
	
	
	
	
	
	Task1_Handle = xTaskCreateStatic( ( TaskFunction_t )Task1_Entry,
	                                  (char *)"Task1",
																		(uint32_t)TASK1_STACK_SIZE,
																		(void *)NULL,
																		(UbaseType_t) 1,               
																		(StackType_t *)Task1Stack,
																		(TCB_t *)&Task1TCB);
//	 
//	vListInsertEnd( &( pxReadyTasksLists[1]), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );

	Task2_Handle = xTaskCreateStatic( ( TaskFunction_t )Task2_Entry,
	                                  (char *)"Task2",
																		(uint32_t)TASK2_STACK_SIZE,
																		(void *)NULL,
																		(UbaseType_t) 2,
																		(StackType_t *)Task2Stack,
																		(TCB_t *)&Task2TCB);
//	 
//	vListInsertEnd( &( pxReadyTasksLists[2]), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
                                    
   portDISABLE_INTERRUPTS();
																		
	
    vTaskStartScheduler();
	for(;;)
	{
			
	}

}
实验现象

实验现象与上一节一样,虽说一样,但是实现延时的方法本质却变了,需要好好理解代码的实现,特别是当系统时基计数器 xTickCount 发生溢出时,延时列表的更换是难点。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/768448.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号