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

FreeRTOS个人笔记-任务延时列表的实现

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

FreeRTOS个人笔记-任务延时列表的实现

根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。

配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!

任务延时列表的实现

在本节之前,为了实现任务的堵塞延时,在任务控制块中内置了一个延时变量,xTickToDelay。

每当任务需要延时时,就初始化xTickToDelay需要延时的时间,然后将任务挂起(只是将任务在优先级位图表uxTopReadyPriority中对应的位清零),并不会将任务从就绪列表中删除。

当每次时基中断(SysTick中断)来临时,就扫描就绪列表中每个任务的xTickToDelay。如果xTickToDelay大于0就递减一次,然后判断xTickToDelay是否为0,如果为0则表示延时时间到,将该任务就绪(将任务在优先级位图表uxTopReadyPriority中对应的位置1),然后进行任务切换。

这种延时的缺点是,在每个时基中断(SysTick中断)中需要对所有任务都扫描一遍,费时。优点是容易理解。

任务延时列表的工作原理

在FreeRTOS中,有两个任务延时列表。

当任务需要延时时,则先将任务挂起,即先将任务从就绪列表删除,然后插入到任务延时列表,同时更新下一个任务的解锁时刻变量:xNextTaskUnblockTime的值。

xNextTaskUnblockTime的值等于系统时基计数器的值xTickCount加上任务需要延时的值xTicksToDelay。即xNextTaskUnblockTime=xTickCount+xTicksToDelay。

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

任务延时列表维护着一条双向链表,每个节点代表了正在延时的任务,节点按照延时时间大小做升序排列。当每次时基中断(SysTick中断)来临时,就拿系统时基计数器的值xTickCount与下一个任务的解锁时刻变量xNextTaskUnblockTime的值相比较,如果相等,则表示有任务延时到期,需要将该任务就绪,否则只是单纯地更新xTickCount的值,然后进行任务切换。

实现任务延时列表

任务延时列表及其指针

//当系统时基计数器xTickCount没有溢出时,用一条列表。当xTickCount溢出时,用另一条列表。
static List_t xDelayedTaskList1;                        //任务延时列表1
static List_t xDelayedTaskList2;                        //任务延时列表2
static List_t * volatile pxDelayedTaskList;             //任务延时列表指针,指向xTickCount没有溢出时使用的那条列表
static List_t * volatile pxOverflowDelayedTaskList;     //任务延时列表指针,指向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;
}

这里的xDelayedTaskList1作为溢出前,xDelayedTaskList2 作为溢出后。

前面说到的xNextTaskUnblockTime,下一个任务的解锁时刻变量。默认定义为0。

static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U;

xNextTaskUnblockTime在vTaskStartScheduler()函数中初始化为portMAX_DELAY。

portMAX_DELAY这个类型不知道大家还有没有印象,附上图look look。

vTaskStartScheduler()函数如下。

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 );           
 
    xNextTaskUnblockTime = portMAX_DELAY;    //此时xNextTaskUnblockTime为最大值0xffff ffff
    xTickCount = ( TickType_t ) 0U;

    
    if( xPortStartScheduler() != pdFALSE )
    {
        
    }
}

持续迭代修改更新。

vTaskDelay()函数

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    
    pxTCB = pxCurrentTCB;
    
    
    //pxTCB->xTicksToDelay = xTicksToDelay;
    //不再依赖TCB结构体的延时变量xTicksToDelay 

    
    prvAddCurrentTaskToDelayedList( xTicksToDelay );
    
    
    taskYIELD();
}

上面函数用到了prvAddCurrentTaskToDelayedList()函数。

要清楚这么一个过程,prvAddCurrentTaskToDelayedList()函数是任务需要延时,在就绪列表中移除,加入到延时列表中。

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( xNextTaskUnblockTime > xTimeToWake )
        {
            xNextTaskUnblockTime = xTimeToWake;
        }
    }	
}

因为 FreeRTOS 支持同一个优先级下可以有多个任务,所以在清除优先级位图表 uxTopReadyPriority 中对应的位时要判断下该优先级下的就绪列表是否还有其它的任务。 

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

xTaskIncrementTick()函数产生大变动。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;  //退出for循环
			}
			else 
			{
                //获取当前列表下第一个节点的TCB
				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();
}

在函数开头,有一个taskSWITCH_DELAYED_LISTS()函数,用于切换延时列表。

//当系统时基计数器溢出的时候,延时列表pxDelayedTaskList 和 pxOverflowDelayedTaskList要互相切换
#define taskSWITCH_DELAYED_LISTS()
{
	List_t *pxTemp;
	pxTemp = pxDelayedTaskList;
	pxDelayedTaskList = pxOverflowDelayedTaskList;
	pxOverflowDelayedTaskList = pxTemp;
	xNumOfOverflows++;
	prvResetNextTaskUnblockTime();
}

xNumOfOverflows为溢出标志位,也算是溢出后列表转换完成位的意思。初始值为0。

static volatile BaseType_t xNumOfOverflows 			= ( BaseType_t ) 0;

上面函数中,用到了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 ) );
	}
}

位于延迟列表最前面的任务应该从阻塞状态中移除。

在没有添加任务延时列表之前,只有就绪列表。无论任务在延时还是就绪都只能通过扫描就绪列表来找到任务TCB,从而实现系统调度。

在上一节“支持多优先级”中,实现taskRESET_READY_PRIORITY()函数时,不用先判断当前优先级下就绪列表中的链表的节点是否为0,而是直接把任务在优先级位图表uxTopReadyPriority中对应位清零。因为当前优先级下就绪列表中的链表节点不可能为0。

在本节中,我们额外添加了延时列表,当任务要延时时,将任务从就绪列表中移除,然后添加到延时列表 。同时将任务在优先级位图表uxTopReadyPriority中对应位清零。

在清除任务在优先级位图表uxTopReadyPriority中对应位时,与上一节不同的是,需要判断就绪列表pxReadyTaskLists[]在当前优先级下对应的链表节点是否为0。

只有当该链表下没有任务时才真正地将任务在优先级位图表uxTopReadyPriority中对应位清零。

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

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

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