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。
留白