freeRTOS 时间管理

1. 相对时间延时
vTaskDelay ->
prvAddCurrentTaskToDelayedList(函数分析之后,有步骤解析)
为什么使用两个延时列表?


2. 绝对时间延时
PreTimeWake、SysTickCnt、TimeWake三者的关系图。


3
. 系统滴答处理函数Increment
步骤总结在下边了。

 

 

 

相对时间延时

#if ( INCLUDE_vTaskDelay == 1 )

    void vTaskDelay( const TickType_t xTicksToDelay )
    {
    BaseType_t xAlreadyYielded = pdFALSE;

        /* A delay time of zero just forces a reschedule. */
        if( xTicksToDelay > ( TickType_t ) 0U )  延时时间如果不大于0,相当于直接进行任务切换
        {
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll();  挂起调度器
            {
                traceTASK_DELAY();

                /* A task that is removed from the event list while the
                scheduler is suspended will not get placed in the ready
                list or removed from the blocked list until the scheduler
                is resumed.

                This task cannot be in an event list as it is the currently
                executing task. */
                prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );  加入到pxDelayedTaskList或者pxOverflowDelayedTaskList中
            }
            xAlreadyYielded = xTaskResumeAll();  如果返回true,说明需要任务切换
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* Force a reschedule if xTaskResumeAll has not already done so, we may
        have put ourselves to sleep. */
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();  置位PendSV
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

#endif /* INCLUDE_vTaskDelay */
  1 static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
  2 {
  3 TickType_t xTimeToWake;
  4 const TickType_t xConstTickCount = xTickCount;  记录时间点,用于任务唤醒。每个systick中断,xTickcount++.
  5 
  6     #if( INCLUDE_xTaskAbortDelay == 1 )
  7     {
  8         /* About to enter a delayed list, so ensure the ucDelayAborted flag is
  9         reset to pdFALSE so it can be detected as having been set to pdTRUE
 10         when the task leaves the Blocked state. */
 11         pxCurrentTCB->ucDelayAborted = pdFALSE;  使能xTaskAbortDelay的话,初始化这个变量字段为false.
 12     }
 13     #endif
 14 
 15     /* Remove the task from the ready list before adding it to the blocked list
 16     as the same list item is used for both lists. */  任务从就绪表移除
 17     if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
 18     {
 19         /* The current task must be in a ready list, so there is no need to
 20         check, and the port reset macro can be called directly. */
 21         portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );  更新变量uxTopReadyPrio,详见任务调度、删除任务章节。
 22     }
 23     else
 24     {
 25         mtCOVERAGE_TEST_MARKER();
 26     }
 27 
 28     #if ( INCLUDE_vTaskSuspend == 1 )
 29     {
 30         if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE 表示允许任务阻塞) )
 31         {
 32             /* Add the task to the suspended task list instead of a delayed task
 33             list to ensure it is not woken by a timing event.  It will block
 34             indefinitely. */    Never Timeout!!!
 35             vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );  直接添加到挂起列表,而不是延时列表!
 36         }
 37         else
 38         {
 39             /* Calculate the time at which the task should be woken if the event
 40             does not occur.  This may overflow but this doesn't matter, the
 41             kernel will manage it correctly. */
 42             xTimeToWake = xConstTickCount + xTicksToWait;  计算任务唤醒时间点...
 43 
 44             /* The list item will be inserted in wake time order. */
 45             listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );  写入 状态列表项value的值
 46 
 47             if( xTimeToWake < xConstTickCount )  任务唤醒时间点 < TickCount,说明数值溢出了(32bit值),溢出的话,将这个任务添加到“溢出延时列表”。
 48             {
 49                 /* Wake time has overflowed.  Place this item in the overflow
 50                 list. */
 51                 vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );  这个插入的API是排序插入的!
 52             }
 53             else
 54             {
 55                 /* The wake time has not overflowed, so the current block list
 56                 is used. */
 57                 vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );  没有溢出,添加到延时列表
 58 
 59                 /* If the task entering the blocked state was placed at the
 60                 head of the list of blocked tasks then xNextTaskUnblockTime
 61                 needs to be updated too. */
 62                 if( xTimeToWake < xNextTaskUnblockTime )  全局变量xNextTaskUnblockTime:距离下一个要取消阻塞的任务最小时间点
 63                 {
 64                     xNextTaskUnblockTime = xTimeToWake;  更新这个最小的时间点
 65                 }
 66                 else
 67                 {
 68                     mtCOVERAGE_TEST_MARKER();
 69                 }
 70             }
 71         }
 72     }
 73     #else /* INCLUDE_vTaskSuspend */  没有定义TaskSuspend列表
 74     {
 75         /* Calculate the time at which the task should be woken if the event
 76         does not occur.  This may overflow but this doesn't matter, the kernel
 77         will manage it correctly. */
 78         xTimeToWake = xConstTickCount + xTicksToWait;
 79 
 80         /* The list item will be inserted in wake time order. */
 81         listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
 82 
 83         if( xTimeToWake < xConstTickCount )
 84         {
 85             /* Wake time has overflowed.  Place this item in the overflow list. */
 86             vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
 87         }
 88         else
 89         {
 90             /* The wake time has not overflowed, so the current block list is used. */
 91             vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
 92 
 93             /* If the task entering the blocked state was placed at the head of the
 94             list of blocked tasks then xNextTaskUnblockTime needs to be updated
 95             too. */
 96             if( xTimeToWake < xNextTaskUnblockTime )
 97             {
 98                 xNextTaskUnblockTime = xTimeToWake;
 99             }
100             else
101             {
102                 mtCOVERAGE_TEST_MARKER();
103             }
104         }
105 
106         /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
107         ( void ) xCanBlockIndefinitely;
108     }
109     #endif /* INCLUDE_vTaskSuspend */
110 }

>1 记录时间点O

>2 将任务从就绪列表删除

>3 根据时间点O,计算唤醒的时间点

>3' 将唤醒时间点,写入任务状态列表项的value里,用于排序插入。

>4 根据唤醒时间点,将任务加入到延时列表或者溢出的延时列表

>5 更新NextTaskUnblockTime

 

为什么有两个延时列表?

当TickCnt没有溢出时,唤醒的时间点 > TickCnt,就有两种情况,溢出或者没溢出

@唤醒时间点没有溢出的任务,将他们放到延时任务列表中,一旦TickCnt == 唤醒时间点时,就出队了。

唤醒时间点溢出的任务,将他们放到溢出延时任务列表,当TickCnt也溢出了,把这两个列表交换一下,

这时原来的延时任务列表肯定是空的了!因为TickCnt都溢出了啊!

所以原来的溢出延时任务列表,变成了当前使用的延时任务列表。又可以参照@处的步骤走了。

相当于溢出延时任务列表时钟是一个当前延时任务列表的拓展,

互相交替使用,无穷尽。

 

 

 

 

绝对时间延时

       params: 上一次结束延时的时间点,本次要延时的节拍数(相对于pxPreviousWakeTime来算),他们的关系见后边的图。
1
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) 2 { 3 TickType_t xTimeToWake; 4 BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE; 5 6 configASSERT( pxPreviousWakeTime ); 7 configASSERT( ( xTimeIncrement > 0U ) ); 8 configASSERT( uxSchedulerSuspended == 0 ); 9 10 vTaskSuspendAll(); 11 { 12 /* Minor optimisation. The tick count cannot change in this 13 block. */ 14 const TickType_t xConstTickCount = xTickCount; 记录时间点 15 16 /* Generate the tick time at which the task wants to wake. */ 17 xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; 任务唤醒时间点。## 时间关系见图。 18 19 if( xConstTickCount < *pxPreviousWakeTime ) 20 { 21 /* The tick count has overflowed since this function was 22 lasted called. In this case the only time we should ever 23 actually delay is if the wake time has also overflowed, 24 and the wake time is greater than the tick time. When this 25 is the case it is as if neither time had overflowed. */ ## TickCount和TimeToWake都溢出。见图。 26 if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) ) 27 { 28 xShouldDelay = pdTRUE; 允许延时 29 } 30 else 31 { 32 mtCOVERAGE_TEST_MARKER(); 33 } 34 } 35 else 36 { 37 /* The tick time has not overflowed. In this case we will 38 delay if either the wake time has overflowed, and/or the 39 tick time is less than the wake time. */ 40 if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) ) 41 { 42 xShouldDelay = pdTRUE; 两种情况都允许延时,(只有TimeToWake溢出 or 都没有溢出)见图 43 } 44 else 45 { 46 mtCOVERAGE_TEST_MARKER(); 47 } 48 } 49 50 /* Update the wake time ready for the next call. */ 51 *pxPreviousWakeTime = xTimeToWake; 更新PreviousWakeTime 52 53 if( xShouldDelay != pdFALSE ) 54 { 55 traceTASK_DELAY_UNTIL( xTimeToWake ); 56 57 /* prvAddCurrentTaskToDelayedList() needs the block time, not 58 the time to wake, so subtract the current tick count. */
还需要的阻塞时间是(TimeToWake - TickCount),而vTaskDelay()函数中,只是简单的设为xTicksToDelay!!
59 prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE ); 60 } 61 else 62 { 63 mtCOVERAGE_TEST_MARKER(); 64 } 65 } 66 xAlreadyYielded = xTaskResumeAll(); 67 68 /* Force a reschedule if xTaskResumeAll has not already done so, we may 69 have put ourselves to sleep. */ 70 if( xAlreadyYielded == pdFALSE ) 防止当前任务阻塞后休眠,这里强制进行一次任务切换。 71 { 72 portYIELD_WITHIN_API(); 73 } 74 else 75 { 76 mtCOVERAGE_TEST_MARKER(); 77 } 78 }

使用绝对延时不一定就能周期性的运行,只能保证按照一定周期取消阻塞。

如果被高优先级任务或者中断打断,这个绝对延时就被破坏了。

使用方法:

TickType_t PreviousWakeTime;

//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接设置延时时间。
const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );  //将50ms转换为系统节拍数。
PreviousWakeTime = xTaskGetTickCount(); //获取当前的系统节拍值

//调用函数 vTaskDelayUntil 进行延时 vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);

 

 

 

========================================================

系统时钟节拍TickCount的处理。

  1 BaseType_t xTaskIncrementTick( void )
  2 {
  3 TCB_t * pxTCB;
  4 TickType_t xItemValue;
  5 BaseType_t xSwitchRequired = pdFALSE;
  6 
  7     /* Called by the portable layer each time a tick interrupt occurs.
  8     Increments the tick then checks to see if the new tick value will cause any
  9     tasks to be unblocked. */  每个tick中断调用一次,增加tick值,判断是否有任务取消阻塞。
 10     traceTASK_INCREMENT_TICK( xTickCount );
 11     if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
 12     {
 13         /* Minor optimisation.  The tick count cannot change in this block. */
 15         const TickType_t xConstTickCount = xTickCount + 1;
 16 
 17         /* Increment the RTOS tick, switching the delayed and overflowed
 18         delayed lists if it wraps to 0. */
 19         xTickCount = xConstTickCount;  交换两个延时列表,如果Tick溢出归零。
 20 
 21         if( xConstTickCount == ( TickType_t ) 0U )
 22         {
 23             taskSWITCH_DELAYED_LISTS();  交换之后,更新uNextTaskUnblockTime
 24         }
 25         else
 26         {
 27             mtCOVERAGE_TEST_MARKER();
 28         }
 29         
uNextTaskUnblockTime保存下一个要解除阻塞的任务时间点值,如果小于等于 TickCnt,则有任务要解除阻塞!! 30 /* See if this tick has made a timeout expire. Tasks are stored in 31 the queue in the order of their wake time - meaning once one task 32 has been found whose block time has not expired there is no need to 33 look any further down the list. */ 34 if( xConstTickCount >= xNextTaskUnblockTime ) 35 { 36 for( ;; ) 37 { 38 if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) 延时任务列表为空,把NextTimeUnblockTime设为最大 39 { 40 /* The delayed list is empty. Set xNextTaskUnblockTime 41 to the maximum possible value so it is extremely 42 unlikely that the 43 if( xTickCount >= xNextTaskUnblockTime ) test will pass 44 next time through. */ 45 xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 */ 46 break; 47 } 48 else 延时任务列表不为空,延时任务列表是按唤醒时间排序的! 49 { 50 /* The delayed list is not empty, get the value of the 51 item at the head of the delayed list. This is the time 52 at which the task at the head of the delayed list must 53 be removed from the Blocked state. */ 54 pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); 55 xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); 56 57 if( xConstTickCount < xItemValue ) 任务唤醒时间点 大于 TickCnt,表示延时时间未到 58 { 59 /* It is not time to unblock this item yet, but the 60 item value is the time at which the task at the head 61 of the blocked list must be removed from the Blocked 62 state - so record the item value in 63 xNextTaskUnblockTime. */ 64 xNextTaskUnblockTime = xItemValue; 更新NTUT... 65 break; 66 } 67 else 68 { 69 mtCOVERAGE_TEST_MARKER(); 70 } 71 72 /* It is time to remove the item from the Blocked state. */ 73 ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); 延时时间到了,先从延时列表移除。 74 75 /* Is the task waiting on an event also? If so remove 76 it from the event list. */ 是否这个任务还在等信号量,如果是,就从这个信号量列表移除,因为超时时间到了!! 77 if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) 列表项的Container是事件容器(某个Queue的WaitToSend/Rcv列表),其Owner是所属任务TCB。 78 { 79 ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); 80 } 81 else 82 { 83 mtCOVERAGE_TEST_MARKER(); 84 } 85 86 /* Place the unblocked task into the appropriate ready list. */ 88 prvAddTaskToReadyList( pxTCB ); 添加到就绪列表 89 90 /* A task being unblocked cannot cause an immediate 91 context switch if preemption is turned off. */ 92 #if ( configUSE_PREEMPTION == 1 ) 93 { 94 /* Preemption is on, but a context switch should 95 only be performed if the unblocked task has a 96 priority that is equal to or higher than the 97 currently executing task. */ 98 if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) 99 { 100 xSwitchRequired = pdTRUE; 这个任务优先级较高,需要任务切换!标记下. 101 } 102 else 103 { 104 mtCOVERAGE_TEST_MARKER(); 105 } 106 } 107 #endif /* configUSE_PREEMPTION */ 108 } 109 } 110 } 111
时间片调度相关内容: 112 /* Tasks of equal priority to the currently running task will share 113 processing time (time slice) if preemption is on, and the application 114 writer has not explicitly turned time slicing off. */ 115 #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) 116 { 117 if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ) 118 { 119 xSwitchRequired = pdTRUE; 当下这个优先级有多个任务,则标记需要任务切换。 120 } 121 else 122 { 123 mtCOVERAGE_TEST_MARKER(); 124 } 125 } 126 #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */ 127 128 #if ( configUSE_TICK_HOOK == 1 ) 时间片钩子函数 129 { 130 /* Guard against the tick hook being called when the pended tick 131 count is being unwound (when the scheduler is being unlocked). */ 132 if( uxPendedTicks == ( UBaseType_t ) 0U ) 133 { 134 vApplicationTickHook(); 135 } 136 else 137 { 138 mtCOVERAGE_TEST_MARKER(); 139 } 140 } 141 #endif /* configUSE_TICK_HOOK */ 142 } 143 else 144 { 145 ++uxPendedTicks; 调度器被挂起时,Tick中断不更新TickCnt,而是更新PendTick。在调度器恢复函数中,处理了PendTick. 146 147 /* The tick hook gets called at regular intervals, even if the 148 scheduler is locked. */ 149 #if ( configUSE_TICK_HOOK == 1 ) 150 { 151 vApplicationTickHook(); 152 } 153 #endif 154 } 155 156 #if ( configUSE_PREEMPTION == 1 ) 157 { 158 if( xYieldPending != pdFALSE ) 159 { 160 xSwitchRequired = pdTRUE; 161 } 162 else 163 { 164 mtCOVERAGE_TEST_MARKER(); 165 } 166 } 167 #endif /* configUSE_PREEMPTION */ 168 169 return xSwitchRequired; 返回值ture表示需要任务切换。xPortSysTickHandler调用xTaskIncrementTick时会判断这个返回值,并决定是否进行任务切换。 170 }

>1 给TickCnt加一,如果溢出,交换两个延时任务列表。

>2 uNextTaskUnblockTime保存下一个要解除阻塞的任务时间点值,如果小于等于 TickCnt,则有任务要解除阻塞!!

>3 从延时任务列表的第一个列表项中取出Value(唤醒时间)

>4 如果这个唤醒时间 大于 TickCnt表示延时没到,更新一下UTUT的值。

>5 反之,则有任务延时时间到了!从延时任务列表中移除。

>6 如果这个任务在等待某个事件,那么就把它从事件的等待列表(WaitToSend/Rcv列表)中踢出去,因为超市时间到了!

>7 添加到就绪任务列表,如果它优先级高,则请求任务切换。

 

时间片的解析不再多说,见任务切换

 

如果调度器被挂起了,那么不能更新TickCnt,改为更新PendTickCnt,

调度器恢复的函数处理中,会把这个时间弥补回去给TickCnt。

 

 

 

 

 

留白

posted @ 2017-11-20 11:39  为民除害  阅读(1361)  评论(0编辑  收藏  举报