記事ディレクトリ
1.FreeRTOS遅延機能
1. 関数 vTaskDelay()
FreeRTOS の遅延機能にも相対モードと絶対モードがありますが、FreeRTOS ではモードごとに異なる関数が使用されます vTaskDelay() 関数は相対モード (相対遅延関数) で、関数 vTaskDelayUntil() は絶対モード (絶対遅延関数) 時間関数)。関数 vTaskDelay() はファイル tasks.c で定義されています。この関数を使用するには、マクロ INCLUDE_vTaskDelay を 1 にする必要があります。関数コードは次のとおりです。
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
//延时时间要大于 0。
if( xTicksToDelay > ( TickType_t ) 0U ) (1)
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (2)
{
traceTASK_DELAY();
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); (3)
}
xAlreadyYielded = xTaskResumeAll(); (4)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xAlreadyYielded == pdFALSE ) (5)
{
portYIELD_WITHIN_API(); (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1) 遅延時間は、遅延するティック数であるパラメーター xTicksToDelay によって決定され、遅延時間は
0 より大きくなければなりません。それ以外の場合は、関数 portYIELD() を直接呼び出してタスクを切り替えることと同じです。
(2) vTaskSuspendAll()関数を呼び出して、タスクスケジューラをサスペンドします。
(3) 関数 prvAddCurrentTaskToDelayedList() を呼び出して、遅延するタスクを遅延リスト pxDelayedTaskList または pxOverflowDelayedTaskList() に追加します。関数 prvAddCurrentTaskToDelayedList() は後で詳しく分析します
。
(4) 関数 xTaskResumeAll() を呼び出して、タスク スケジューラを復元します。
(5) 関数 xTaskResumeAll() がタスク スケジューリングを実行しない場合は、ここでタスク スケジューリングを実行する必要があります。
(6) 関数 portYIELD_WITHIN_API() を呼び出して、タスクをスケジュールします。
2. 関数 prvAddCurrentTaskToDelayedList()
関数 prvAddCurrentTaskToDelayedList() は、現在のタスクを待機リストに追加するために使用されます。この関数は
ファイル tasks.c で定義されています。短縮された関数は次のとおりです。
static void prvAddCurrentTaskToDelayedList( TickType_t x TicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; (1)
#if( INCLUDE_xTaskAbortDelay == 1 )
{
//如果使能函数 xTaskAbortDelay()的话复位任务控制块的 ucDelayAborted 字段为
//pdFALSE。
pxCurrentTCB->ucDelayAborted = pdFALSE;
}
#endif
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (2)
{
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); (3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )(4)
{
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); (5)
}
else
{
xTimeToWake = xConstTickCount + xTicksToWait; (6)
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), \ (7)
xTimeToWake );
if( xTimeToWake < xConstTickCount ) (8)
{
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->\ (9)
xStateListItem ) );
}
else
{
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); (10)
if( xTimeToWake < xNextTaskUnblockTime ) (11)
{
xNextTaskUnblockTime = xTimeToWake; (12)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
/***************************************************************************/
/****************************其他条件编译语句*******************************/
/***************************************************************************/
}
}
(1). 関数 prvAddCurrentTaskToDelayedList() に入る時点を読み取り、後でタスクの起床時点を計算するときに使用する xConstTickCount に保存します。xTickCount はクロック ティック カウンターで、各ティック タイマー割り込み xTickCount は 1 ずつ増加します。
(2) 現在実行中のタスクを遅延リストに追加するには、まず現在のタスクを実行可能リストから削除する必要があります。
(3) 現在のタスクを実行可能リストから削除した後、uxTopReadyPriority でタスクの実行可能マークを解除します。
つまり、uxTopReadyPriority の対応するビットをクリアします。
(4) 遅延時間が最大値 portMAX_DELAY で、xCanBlockIndefinitely が pdFALSE でない場合 (xCanBlockIndefinitely が pdFALSE でない場合は、ブロックしているタスクが許可されていることを意味します)、現在のタスクが直接保留リストに追加され、タスクが実行されます。遅延リストに追加する必要はありません。
(5). 現在のタスクを中断リスト xSuspendedTaskList の末尾に追加します。
(6) (1)で取得したエントリ関数 prvAddCurrentTaskToDelayedList()の時間値 xConstTickCount に遅延時間値 xTicksToWait を加算したタスクの起床時刻を計算します。
(7) 計算されたタスク起床時間値 xTimeToWake を、タスク リストの状態リスト項目の対応するフィールドに書き込みます
。
(8) 計算されたタスク起床時刻が xConstTickCount 未満であり、オーバーフローが発生したことを示します。グローバル変数
xTickCount は 32 ビット データ型である TickType_t 型であるため、xTimeToWake を使用してタスクのウェイクアップ時間を計算すると、オーバーフローが確実に発生します。FreeRTOS はこの現象に特別に対処した. FreeROTS では xDelayedTaskList1 と xDelayedTaskList2 の 2 つの遅延リストが定義されており, これら 2 つのリストにアクセスするために pxDelayedTaskList と pxOverflowDelayedTaskList の 2 つのポインタが定義されている. 初期化リスト関数 prvInitialiseTaskLists() では, ポインタ pxDelayedTaskList を指す.リスト xDelayedTaskList1、およびポインター pxOverflowDelayedTaskList はリスト xDelayedTaskList2 を指します。このように、オーバーフローが発生した場合、タスクは pxOverflowDelayedTaskList が指すリストに追加され、オーバーフローが発生しなければ、pxDelayedTaskList が指すリストに追加されます。
(9). オーバーフローが発生した場合、現在のタスクを pxOverflowDelayedTaskList が指すリストに追加します。
(10). オーバーフローが発生しない場合は、現在のタスクを pxDelayedTaskList が指すリストに追加します。
(11)、xNextTaskUnblockTime はグローバル変数であり、ブロックを解除する次のタスクからの最小
時点値を保存します。xTimeToWake が xNextTaskUnblockTime よりも小さい場合、より短い時点が近づいていることを意味します。
(12). xNextTaskUnblockTime を xTimeToWake に更新します。
3. 関数 vTaskDelayUntil()
関数 vTaskDelayUntil() はタスクをブロックします, ブロック時間は絶対時間です.特定の頻度で
実行する必要があるタスクは、関数 vTaskDelayUntil() を使用できます. この関数はファイル tasks.c に次のように定義されています。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT( pxPreviousWakeTime );
configASSERT( ( xTimeIncrement > 0U ) );
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (1)
{
const TickType_t xConstTickCount = xTickCount; (2)
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; (3)
if( xConstTickCount < *pxPreviousWakeTime ) (4)
{
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake >\ (5)
xConstTickCount ) )
{
xShouldDelay = pdTRUE; (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > \ (7)
xConstTickCount ) )
{
xShouldDelay = pdTRUE; (8)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
*pxPreviousWakeTime = xTimeToWake; (9)
if( xShouldDelay != pdFALSE ) (10)
{
traceTASK_DELAY_UNTIL( xTimeToWake );
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );(11)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll(); (12)
if( xAlreadyYielded == pdFALSE )
{
ortYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
パラメーター:
pxPreviousWakeTime:遅延が終了した後にタスクが起動された時刻関数 vTaskDelayUntil がタスクで初めて呼び出される場合、
pxPreviousWakeTime を初期化して while() ループ本体の時刻値を入力する必要があります。タスク。関数 vTaskDelayUntil() は、将来の実行で pxPreviousWakeTime を自動的に更新します。
xTimeIncrement:タスクが遅延する必要があるタイム ビートの数 (この遅延の pxPreviousWakeTime のビート数に相対的)。
(1) タスクスケジューラをサスペンドします。
(2). 関数 vTaskDelayUntil() に入る時点の値を記録し、xConstTickCount に保存します。
(3) 遅延時間 xTimeIncrement からタスクの次の起床時刻を計算し、
xTimeToWake に保存します。この遅延時間は、最後のタスクが起動された時間である pxPreviousWakeTime に関連していることがわかります。pxPreviousWakeTime、xTimeToWake、xTimeIncrement、および xConstTickCount の関係を下図に示します。
上の図に示すように、(1) はタスクの本体、つまりタスクによって実行される実際の作業であり、(2) タスク関数で vTaskDelayUntil() を呼び出してタスクを遅延させており、(3) ) は他のタスクを実行しています。タスクの遅延時間は xTimeIncrement です。この遅延時間は pxPreviousWakeTime に関連しています。タスクの合計実行時間は、タスクの遅延時間 xTimeIncrement 未満でなければならないことがわかります。つまり、vTaskDelayUntil() を使用する場合、タスクの実行期間は xTimeIncrement に相当し、この時間内にタスクを実行する必要があります。これにより、タスクが常に特定の頻度で実行されるようになります.この遅延値は絶対遅延時間であるため、関数 vTaskDelayUntil() は絶対遅延関数とも呼ばれます.
(4) 図 12.3.1 によると、理論的には xConstTickCount が pxPreviousWakeTime よりも大きいことがわかりますが、xConstTickCount が pxPreviousWakeTime よりも小さくなる、つまり xConstTickCount がオーバーフローする状況もあります!
(5) xConstTickCount がオーバーフローしたため、計算されたタスク起床時間もオーバーフローする必要があり、
xTimeToWake は xConstTickCount より大きくなければなりません。(6). 条件 (5) が満たされる場合、pdTRUE を xShouldDelay に割り当て、時間遅延を許容としてマークします
。
(7) 他に 2 つのケースがあります。xTimeToWake のみがオーバーフローする場合は
下図のようになり、オーバーフローしない場合は図 12.3.1 のようになり、どちらの場合も遅延は許容されます。
(8) pdTRUE を xShouldDelay に割り当てて、許容される時間遅延をマークします。
(9). pxPreviousWakeTime の値を xTimeToWake に更新して、この関数の次の実行に備えます
。
(10) 前回の判定後、タスクを遅らせることができる。
(11) 関数 prvAddCurrentTaskToDelayedList() を呼び出して時間を遅らせます。関数の最初のパラメーターは、
タスクのブロック時間を設定することです. タスクの次のウェイクアップ時間を前に計算したので、タスクがまだブロックする必要がある時間は、次のウェイクアップ時間 xTimeToWake から現在の時間 xConstTickCount。関数 vTaskDelay() では、このパラメーターは単純に xTicksToDelay に設定されます。
(12) 関数 xTaskResumeAll() を呼び出して、タスク スケジューラを復元します。
関数 vTaskDelayUntil() は次のように使用されます。
void TestTask( void * pvParameters )
{
TickType_t PreviousWakeTime;
//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
//设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
PreviousWakeTime = xTaskGetTickCount(); //获取当前的系统节拍值
for( ;; )
{
/******************************************************************/
/*************************任务主体*********************************/
/******************************************************************/
//调用函数 vTaskDelayUntil 进行延时
vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
}
}
実際には、関数 vTaskDelayUntil() を使用して遅延したタスクは、必ずしも定期的に実行できるとは限りません. 関数 vTaskDelayUntil() を使用すると、特定の期間に従ってブロックを解除し、準備完了状態に入ることが保証されるだけです. より高い優先度または割り込みがある場合でも、他の優先度の高いタスクまたは割り込みサービス機能が完了するのを待たなければなりません。この絶対的な遅延は、単純な遅延関数 vTaskDelay() にのみ関連しています。
2. FreeRTOS システムクロックビート
システムが何であれ、実行するにはシステム クロック ティックが必要です. これは以前に何度も言及されています. xTickCount は、
FreeRTOS のシステム クロック ティック カウンタです。xTickCount は、各ティック タイマー割り込みで 1 つの xTickCount を追加します. 具体的な操作プロセスは、ファイル tasks.c で定義されている関数 xTaskIncrementTick() で次のように実行されます。
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
//每个时钟节拍中断(滴答定时器中断)调用一次本函数,增加时钟节拍计数器 xTickCount 的
//值,并且检查是否有任务需要取消阻塞。
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) (1)
{
const TickType_t xConstTickCount = xTickCount + 1; (2)
//增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列
//表指针值。
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) (3)
{
taskSWITCH_DELAYED_LISTS(); (4)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升
//序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的
//延时时间都没有到的话后面的任务就不用看了,肯定也没有到。
if( xConstTickCount >= xNextTaskUnblockTime ) (5)
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) (6)
{
//延时列表为空,设置 xNextTaskUnblockTime 为最大值。
xNextTaskUnblockTime = portMAX_DELAY; (7)
break;
}
else
{
//延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值
//判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。
pxTCB = ( TCB_t * )\ (8)
listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue =\ (9)
listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue ) (10)
{
//任务延时时间还没到,但是 xItemValue 保存着下一个即将解除
//阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新
//变量 xNextTaskUnblockTime
xNextTaskUnblockTime = xItemValue; (11)
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//将任务从延时列表中移除
( void ) uxListRemove( &( pxTCB->xStateListItem ) ); (12)
//任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些
//任务从相应的事件列表中移除。相当于等待事件超时退出!
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ (13)
= NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) ); (14)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//将任务添加到就绪列表中
prvAddTaskToReadyList( pxTCB ); (15)
#if ( configUSE_PREEMPTION == 1 )
{
//使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
//运行的任务优先级,如果是的话就需要进行一次任务切换!
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) (16)
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
//如果使能了时间片的话还需要处理同优先级下任务之间的调度
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )(17)
{
if( listCURRENT_LIST_LENGTH( &( \
pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
//使用时钟节拍钩子函数
#if ( configUSE_TICK_HOOK == 1 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
vApplicationTickHook(); (18)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICK_HOOK */
}
else //任务调度器挂起 (19)
{
++uxPendedTicks; (20)
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE ) (21)
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired; (22)
}
}
(1). タスク スケジューラが中断されているかどうかを確認します。
(2) クロック ティック カウンタ xTickCount に 1 を加算し、その結果を xConstTickCount に保存する 次のプログラムは、xTickCount
に xConstTickCount を割り当てます。
(3)、xConstTickCount は 0 であり、オーバーフローが発生したことを示しています!
(4) オーバーフローが発生した場合は、関数 taskSWITCH_DELAYED_LISTS を使用して、
遅延リスト ポインター pxDelayedTaskList とオーバーフロー リスト ポインター pxOverflowDelayedTaskList が指すリストを交換します。 2 つのポインターが指すリストが交換された後、xNextTaskUnblockTime の値を更新する必要があります。
(5). 変数 xNextTaskUnblockTime は、次にブロックを解除するタスクの時点の値を格納します.
xConstTickCount が xNextTaskUnblockTime より大きい場合、ブロックを解除する必要があるタスクがあることを意味します。
(6). 遅延リストが空かどうかを判断します。
(7) 遅延リストが空の場合、xNextTaskUnblockTime を portMAX_DELAY に設定します。
(8) 遅延リストは空ではなく、遅延リストの最初の項目に対応するタスク制御ブロックが取得されます。
(9). (8)で取得したタスク制御ブロック内の強状態リスト項目の値を取得します。
(10) タスク コントロール ブロックのストロング ステート リスト項目の値は、ウェイクアップ時点の値が現在のシステム クロック (クロック ティック カウンタ値) より大きい場合、タスクのウェイクアップ時点を保持しています
。 、タスクの遅延時間はまだ到着していません。
(11) タスクの遅延時間はまだ到来しておらず、xItemValueは次に起床するタスクの起床時刻を保存しているため、xNextTaskUnblockTime を xItemValue で更新する必要があります。
(12) タスクの遅延時間が経過しているため、まずタスクを遅延リストから削除します。
(13) タスクが、セマフォやキューなどのイベントを待っているかどうかを確認します。それでも待機している場合、タスクは
対応するイベント リストから削除されます。タイムアウトだから!
(14). 対応するイベント リストからタスクが削除されます。
(15) タスクの遅延時間が経過し、タスクが遅延リストまたはイベント リストから削除されました。ここで、
タスクを準備完了リストに追加する必要があります。
(16). 遅延時間が経過したタスクの優先度が実行中のタスクよりも高いため、タスクの切り替えが必要です. xSwitchRequired を pdTRUE としてマークし、タスクの切り替えが必要であることを示します
.
(17) タイム スライス スケジューリングが有効な場合、タイム スライス スケジューリングに関連する作業も処理する必要があります。具体的なプロセスについては、9.6 節を参照してください。
(18) タイムスライスフック機能が有効な場合、タイムスライスフック関数 vApplicationTickHook() を実行し、
関数の具体的な内容をユーザーが記述します。
(19). 関数 vTaskSuspendAll() がタスク スケジューラを一時停止するために呼び出される場合、
xTickCount はティック タイマー割り込みごとに更新されません。代わりに、uxPendedTicks を使用して、スケジューラが中断されたティック数を記録します。このように、関数 xTaskResumeAll() がタスク スケジューラを復元するために呼び出されると、関数 xTaskIncrementTick() が uxPendedTicks 回呼び出されるため、xTickCount が再開され、ブロックを解除する必要があるタスクのブロックが解除されます。
関数 xTaskResumeAll() の対応する処理コードは次のとおりです。
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
configASSERT( uxSchedulerSuspended );
taskENTER_CRITICAL();
/************************************************************************/
/****************************省略部分代码********************************/
/************************************************************************/
UBaseType_t uxPendedCounts = uxPendedTicks;
if( uxPendedCounts > ( UBaseType_t ) 0U )
{
//do-while()循环体,循环次数为 uxPendedTicks
do
{
if( xTaskIncrementTick() != pdFALSE ) //调用函数 xTaskIncrementTick
{
xYieldPending = pdTRUE; //标记需要进行任务调度。
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedCounts; //变量减一
} while( uxPendedCounts > ( UBaseType_t ) 0U );
uxPendedTicks = 0; //循环执行完毕,uxPendedTicks 清零
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/************************************************************************/
/****************************省略部分代码********************************/
/************************************************************************/
taskEXIT_CRITICAL();
return xAlreadyYielded;
}
(20) uxPendedTicks は、ファイル tasks.c で定義されるグローバル変数です。タスク スケジューラが中断された後、この変数は
クロック ティック数を記録するために使用されます。
(21). 場合によっては、他の API 関数を呼び出すときに、変数 xYieldPending を使用して、コンテキストの切り替えが必要かどうかをマークし
、後で特定の分析に遭遇します。
(22) xSwitchRequired の値を返します。xSwitchRequired はタスク切り替えを行うかどうかの情報を保存し、pdTRUE の場合はタスク切り替えを行う必要があり、pdFALSE の場合はタスク切り替えを行う必要はありません。関数xPortSysTickHandler()
内で xTaskIncrementTick() を呼び出すと、戻り値を判断し、戻り値に応じてタスクを切り替えるかどうかを決定します。