FreeRTOS学习记录(八):任务延时列表

2022-04-26

依据:[野火]《FreeRTOS内核实现与应用开发实战指南》


目录

一、任务延时列表工作原理

二、任务延时列表

1、定义任务延时列表

2、任务延时列表初始化

3、定义xNextTaskUnblockTime

4、初始化 xNextTaskUnblockTime

三、修改代码、支持任务延时列表

1、修改 vTaskDelay()函数

prvAddCurrentTaskToDelayedList()函数

2、修改 xTaskIncrementTick()函数

taskSWITCH_DELAYED_LISTS()函数

prvResetNextTaskUnblockTime 函数

3、修改taskRESET_READY_PRIORITY()函数


首先列出FreeRTOS的任务列表作为参考:列表分区不同

优先级问题: ①ReadyList ②DelayList③PendingList

就绪:5个优先级

pxReadyTasksLists[4]

pxReadyTasksLists[3]

pxReadyTasksLists[2]

pxReadyTasksLists[1]

pxReadyTasksLists[0]

等待:

pxDelayedTasksListe

阻塞:

pxPendingTasksListe

想知道内部实现方法流程,还可以看韦东山的教程以及CSDN博客。

前面学习中,为了实现任务的阻塞延时,在任务控制块中内置了一个延时变量 xTicksToDelay。每当任务需要延时的时候,就初始化 xTicksToDelay 需要延时的时间,然 后将任务挂起,这里的挂起只是将任务在优先级位图表 uxTopReadyPriority 中对应的位清 零,并不会将任务从就绪列表中删除。当每次时基中断(SysTick 中断)来临时,就扫描就绪列表中的每个任务的 xTicksToDelay,如果 xTicksToDelay 大于 0 则递减一次,然后判断 xTicksToDelay 是否为 0,如果为 0 则表示延时时间到,将该任务就绪(即将任务在优先级 位图表 uxTopReadyPriority 中对应的位置位),然后进行任务切换。

缺点:在每个时基中断中需要对所有任务都扫描一遍

一、任务延时列表工作原理

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

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

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

二、任务延时列表

1、定义任务延时列表

//tasks.c
/*< Delayed tasks. */
/*< 延迟任务。*/
PRIVILEGED_DATA static List_t xDelayedTaskList1;        //(1)
/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
/*<延迟任务(使用两个列表-一个用于超出当前计时计数的延迟。 */
PRIVILEGED_DATA static List_t xDelayedTaskList2;         //(2)
/*< Points to the delayed task list currently being used. */
/*<指向当前正在使用的延迟任务列表。*/                       
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;         //(3)
/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */ 
/*<指向当前用于保存超出当前勾选计数的任务的延迟任务列表。*/            
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;      //(4)    

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

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

(4)任务延时列表指针,指向 xTickCount 溢出时使用的那条列表

2、任务延时列表初始化

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

//tasks.c
/*-----------------------------------------------------------*/
/* 初始化任务相关的列表 */
static void prvInitialiseTaskLists( void )
{
    UBaseType_t uxPriority;

    /* 初始化就绪列表 */
    for( uxPriority = ( UBaseType_t ) 0U; 
                        uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; 
                        uxPriority++ )
    {
        vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    }

    vListInitialise( &xDelayedTaskList1 );
    vListInitialise( &xDelayedTaskList2 );
    vListInitialise( &xPendingReadyList );


    /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
     * using list2. */
    pxDelayedTaskList = &xDelayedTaskList1;
    pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
/*-----------------------------------------------------------*/

3、定义xNextTaskUnblockTime

//tasks.c
/* Initialised to portMAX_DELAY before the scheduler starts. */
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U; 

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

4、初始化 xNextTaskUnblockTime

NextTaskUnblockTimevTaskStartScheduler()函数中初始化为 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

初始化 xNextTaskUnblockTime

//tasks.c
/*-----------------------------------------------------------*/
void vTaskStartScheduler( void )
{
    /*==================创建空闲任务 start=========================*/
    TCB_t *pxIdleTaskTCBBuffer = NULL;

    StackType_t * pxIdleTaskStackBuffer = NULL;
    uint32_t ulIdleTaskStackSize;
    
    /* 获取空闲任务的内存:任务栈和任务 TCB */
    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 ); 

    /*======================创建空闲任务 end===================*/
    xNextTaskUnblockTime = portMAX_DELAY;
       
    xTickCount = ( TickType_t ) 0U;
    
    /* 启动调度器 */
    if( xPortStartScheduler() != pdFALSE )
    {
        /* 调度器启动成功,则不会返回,即不会来到这里 */
    }

}
/*-----------------------------------------------------------*/

三、修改代码、支持任务延时列表

继续加入任务列表功能!

1、修改 vTaskDelay()函数

//vTaskDelay()函数
void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;

    /* 获取当前任务的 TCB */
    pxTCB = pxCurrentTCB;

    /* 设置延时时间 */ 
    //pxTCB->xTicksToDelay = xTicksToDelay;     //(1) 
 
    /* 将任务插入到延时列表 */ 
    prvAddCurrentTaskToDelayedList( xTicksToDelay );     //(2) 

    /* 任务切换 */
    taskYIELD();
}

(1)添加了任务的延时列表,延时的时候不用再依赖任 务 TCB 中内置的延时变量 xTicksToDelay。

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

prvAddCurrentTaskToDelayedList()函数

//tasks.c
tatic void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait,
                                            const BaseType_t xCanBlockIndefinitely )
{
    TickType_t xTimeToWake;

    /* 获取系统时基计数器 xTickCount 的值 */
    const TickType_t xConstTickCount = xTickCount;        //(1)


    if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )    //(2)
    {
        /* The current task must be in a ready list, so there is no need to
         * check, and the port reset macro can be called directly. */
        portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); 
        /*lint !e931 pxCurrentTCB cannot change as it is the calling task.
         *pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called 
         *with scheduler suspended or in a critical section. */
    }
    
    /* 计算任务延时到期时,系统时基计数器 xTickCount 的值是多少 */    //(3)
    xTimeToWake = xConstTickCount + xTicksToWait;

    /* 将延时到期的值设置为节点的排序值 */             //(4)
    /* The list item will be inserted in wake time order. */
    listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
    
    /* 溢出 */             //(5)
    if( xTimeToWake < xConstTickCount )
    {
        /* Wake time has overflowed.  Place this item in the overflow list. */
        vListInsert( pxOverflowDelayedTaskList, 
                     &( pxCurrentTCB->xStateListItem ) );
    }
    else /* 没有溢出 */
    {
        /* The wake time has not overflowed, so the current block list is used. */
        vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );    //(6)

        /* If the task entering the blocked state was placed at the
         * head of the list of blocked tasks then xNextTaskUnblockTime
         * needs to be updated too. */
        /* 更新下一个任务解锁时刻变量 xNextTaskUnblockTime 的值 */        //(7)
        if( xTimeToWake < xNextTaskUnblockTime )
        {
            xNextTaskUnblockTime = xTimeToWake;
        }                
    }
}

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

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

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

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

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

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

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

2、修改 xTaskIncrementTick()函数

//tasks.c
//稍微改的
/*----------------------------------------------------------*/

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    
    const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;    //(1)

    /* Increment the RTOS tick, switching the delayed and overflowed
     * delayed lists if it wraps to 0. */
    xTickCount = xConstTickCount;
    /* 如果 xConstTickCount 溢出,则切换延时列表 */    //(2) 
    if( xConstTickCount == ( TickType_t ) 0U ) 
    /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
    {
        taskSWITCH_DELAYED_LISTS();
    }
 
    /* 最近的延时任务延时到期 */    //(3)  
    if ( xConstTickCount >= xNextTaskUnblockTime ) 
    {
        for( ; ; )
        {
            if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )    //(4)
            {
                /* The delayed list is empty.  Set xNextTaskUnblockTime
                 * to the maximum possible value so it is extremely
                 * unlikely that the
                 * if( xTickCount >= xNextTaskUnblockTime ) test will pass
                 * next time through. */
                /* 延时列表为空,设置 xNextTaskUnblockTime 为可能的最大值 */
                xNextTaskUnblockTime = portMAX_DELAY; 
                /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

                break;
            }
            else/* 延时列表不为空 */    //(5)
            {
                /* The delayed list is not empty, get the value of the
                 * item at the head of the delayed list.  This is the time
                 * at which the task at the head of the delayed list must
                 * be removed from the Blocked state. */
                pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); 
                /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                
                xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );    //(6)

                /* 直到将延时列表中所有延时到期的任务移除才跳出 for 循环 */    //(7)
                if( xConstTickCount < xItemValue )
                {
                    /* It is not time to unblock this item yet, but the
                     * item value is the time at which the task at the head
                     * of the blocked list must be removed from the Blocked
                     * state -  so record the item value in
                     * xNextTaskUnblockTime. */
                    xNextTaskUnblockTime = xItemValue;
                    break; /*lint !e9011 Code structure here is deedmed easier to understand with multiple breaks. */
                }

                /* 将任务从延时列表移除,消除等待状态 */    //(8)
                ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                /* 将解除等待的任务添加到就绪列表 */ 
                prvAddTaskToReadyList( pxTCB );    //(9)

                    
            }
        }
    }/* xConstTickCount >= xNextTaskUnblockTime */ 

    /* 任务切换 */
    portYIELD();    //(10) 
}
/*-----------------------------------------------------------*/

(1)更新系统时基计数器 xTickCount 的值。

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

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

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

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

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

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

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

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

(10)执行一次任务切换。

taskSWITCH_DELAYED_LISTS()函数

//tasks.c
#define taskSWITCH_DELAYED_LISTS()                                                
    {                                                                             
        List_t * pxTemp;    //(1)                                                          
                                                                                  
        /* The delayed tasks list should be empty when the lists are switched. */ 
        configASSERT( ( listLIST_IS_EMPTY( pxDelayedTaskList ) ) );               
                                                                                  
        pxTemp = pxDelayedTaskList;                                               
        pxDelayedTaskList = pxOverflowDelayedTaskList;                            
        pxOverflowDelayedTaskList = pxTemp;                                       
        xNumOfOverflows++;                                                        
        prvResetNextTaskUnblockTime();    //(2)                                            
    }

/*-----------------------------------------------------------*/

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

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

prvResetNextTaskUnblockTime 函数

//tasks.c
/*-----------------------------------------------------------*/

static void prvResetNextTaskUnblockTime( void )
{
    TCB_t *pxTCB;

    if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )    //(1)
    {
        /* The new current delayed list is empty.  Set xNextTaskUnblockTime to
         * the maximum possible value so it is  extremely unlikely that the
         * if( xTickCount >= xNextTaskUnblockTime ) test will pass until
         * there is an item in the delayed list. */
        /* 当前延时列表为空,则设置 xNextTaskUnblockTime 等于最大值 */
        xNextTaskUnblockTime = portMAX_DELAY;
    }
    else    //(2)
    {

       /* 当前列表不为空,则有任务在延时,则获取当前列表下第一个节点的排序值
        *然后将该节点的排序值更新到 xNextTaskUnblockTime */
       ( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );

        /* The new current delayed list is not empty, get the value of
         * the item at the head of the delayed list.  This is the time at
         * which the task at the head of the delayed list should be removed
         * from the Blocked state. */
        xNextTaskUnblockTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxDelayedTaskList );
    }
}
/*-----------------------------------------------------------*/

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

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

3、修改taskRESET_READY_PRIORITY()函数

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

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

//tasks.c
#if 1    /* 本章的实现方法 */ //源代码实现方法!!!
    /* A port optimised version is provided, call it only if the TCB being reset
     * is being referenced from a ready list.  If it is referenced from a delayed
     * or suspended list then it won't be in a ready list. */
    #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

实现了FreeRTOS中的延时问题。从就绪列表移动到延时列表

系统时基计数器 xTickCount 发生溢出时,延时列表的更换是难点

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值