FreeRTOS Code Reading (Arduino編, #3)
今回は予定通り vTaskStartScheduler() から呼ばれる xTaskCreate() を読んでいく。
vTaskStartScheduler()
からの の呼び出し。
- variantHooks.cpp: void vTaskStartScheduler( void )
/* The Idle task is being created using dynamically allocated RAM. */ xReturn = xTaskCreate( prvIdleTask, configIDLE_TASK_NAME, configMINIMAL_STACK_SIZE, ( void * ) NULL, portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */ &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
- tack.h: 関数宣言
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) PRIVILEGED_FUNCTION;
- 関数引数
引数型 | 名前 | 内容、意味 |
---|---|---|
TaskFunction_t | pxTaskCode | PRiVate アイドルタスク(関数)、 |
const char * const | pcName | タスク名。configIDLE_TASK_NAME, "IDLE"。 |
const configSTACK_DEPTH_TYPE | usStackDepth | タスクのスタックサイズ。configMINIMAL_STACK_SIZE, 192byte |
void * const | pvParameters | タスクへの引数。null pointer |
UBaseType_t | uxPriority | 0x00。portPRIVILEGE_BIT |
TaskHandle_t * const | pxCreatedTask | NULL。xIdleTaskHandle |
次はxTaskCreate()
の実装。
- tack.c
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask { ... #if( portSTACK_GROWTH > 0 ) ... #else /* portSTACK_GROWTH */ { StackType_t *pxStack; /* Allocate space for the stack used by the task being created. */ pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */ if( pxStack != NULL ) { /* Allocate space for the TCB. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */ if( pxNewTCB != NULL ) { /* Store the stack location in the TCB. */ pxNewTCB->pxStack = pxStack; } else { ... } } else { ... } } #endif /* portSTACK_GROWTH *
順にみていくが、pvPortMalloc()はヒープからメモリを確保していると思うので詳細は後で見る。
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
タスクのためのスタックメモリ領域を確保。サイズは StackType_t を usStackDepth個 だけ。
次にタスク制御バッファ(Tack Control Buffer。TCB_t
)の領域を確保する。
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
#defineを辿るとTCB_t
の実体は下記のようである。
- tack.c:
typedef struct TaskControlBlock_t { volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */ ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ ListItem_t xEventListItem; /*< Used to reference a task from an event list. */ UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */ StackType_t *pxStack; /*< Points to the start of the stack. */ char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ }
メンバの型 | メンバ変数名 | 内容、意味 |
---|---|---|
volatile StackType_t | *pxTopOfStack | タスクのスタックの最後のアイテムのアドレスへのポインタ。TCB構造体のメンバの最初に置く必要がある |
ListItem_t | xStateListItem | ステート(状態)リスト。Ready, Blocked, Suspended |
ListItem_t | xEventListItem | イベントリストからタスクを参照するのに使われる |
UBaseType_t | uxPriority | 優先度。0が最低。 |
StackType_t | *pxStack | スタックの先頭アドレスへのポインタ |
char | pcTaskName[ configMAX_TASK_NAME_LEN ] | デバッグ用 |
xStateListItem、xEventListItem の使われ方がまだよくわからない。
pxNewTCB->pxStack = pxStack;
タスク用のスタック領域(pxStack )と、タスク制御用のバッファ(pxNewTCB )が確保出来たら、タスク制御用のバッファのスタック領域に割り当てる。
後回しにしていたpvPortMalloc()
を詳細にみてみる。
- heap_3.c
void *pvPortMalloc( size_t xWantedSize ) { void *pvReturn; vTaskSuspendAll(); { pvReturn = malloc( xWantedSize ); traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll(); #if( configUSE_MALLOC_FAILED_HOOK == 1 ) { if( pvReturn == NULL ) { ... } } #endif return pvReturn; }
メモリをヒープから確保する。下記順に実行されている
- タスクを全部サスペンドする(
vTaskSuspendAll()
) - malloc してヒープからメモリをxWantedSize [byte] 確保する。
- サスペンドしたタスクをレジュームする(
xTaskResumeAll()
)
まず、1. の全タスクのサスペンドする処理を読んでみる。
- tasks.c:
void vTaskSuspendAll( void ) { /* A critical section is not required as the variable is of type BaseType_t. Please read Richard Barry's reply in the following link to a post in the FreeRTOS support forum before reporting this as a bug! - http://goo.gl/wu4acr */ ++uxSchedulerSuspended; }
コードは1行しかないけど、コメントが何やら書いてあるので辿ってみる。 goo.gl
要約すると、最初にこの記述は問題があるのでは?という質問に対して、問題ない、と回答している。 質問者が下記質問を投げかけている。
AVR(Arduino UNOとか), ARM Cortex M ではこの操作(++uxSchedulerSuspended; のこと)をコンパイラーが以下のように処理する
- (メモリから)uxSchedulerSuspendeded をCPUレジスタにロードする
- レジスタ値をインクリメントする
- レジスタ値を (メモリ上の)uxSchedulerSuspendeded にストアする
これは 明らかに NOT ATOMIC じゃない。(訳注:3命令の途中でタスク切り替えが発生し得る etc.) だから、アトミックなインクリメント機械語命令がないすべてのプラットフォームに対してportATOMIC_INCREMENT()マクロを実装することを提案する。
それに対する richardbarry さん(中の人?)の答えがこれ。
各タスクは自分自身のコンテキストを管理していて、かつコンテキストスイッチはこの値(
uxSchedulerSuspended
)がゼロでないときは発生しない。 従って、レジスタバックからメモリーへの書き込みはアトミックであり、問題にならない。
さらに丁寧に例を挙げて説明してくれている。
uxSchedulerSuspended がゼロから始まるシナリオを考えてみる。
- (メモリから)uxSchedulerSuspendeded をCPUレジスタにロードする
さて、コンテキストスイッチは他のタスクが実行することを発生させる。そのタスクが同じ変数を使用する。そのタスクはその変数をゼロとしてみる。なぜなら、その変数はオリジナルタスクから更新されていないから。
ここまでは理解できる。
そのあとで、オリジナルタスクが再度実行したとする。これは以下のときのみ発生する
- uxSchedulerSuspended が再度ゼロに設定された
- かつ、オリジナルタスクがCPUレジスタの内容、これらはコンテキストスイッチアウトされた時のまま保持された内容であり、これを再度実行した
従って、レジスタにリードされた値はまだ(メモリ上の)
xuSchedulerSuspended
の値と同じである。
うーん。この条件がよくわからない。
さらにこの後に続く質問者のコメントをみると、
uxSchedulerSuspended
はカウンターではなくバイナリ(1 or 0)で扱われているから問題ないね。
と納得しているのでなんかそういうことなんだと思うけど、あとでもう一度考えてみます。
malloc() は stdlib.h で宣言されている。malloc実装はあとでみる。 mallocを解説した動画www.youtube.comも参考になる。
traceMALLOC()
は、traceMALLOC
が定義されておらずArduino UNO版では実態はないようだ。
- Arduino_FreeRTOS.h
#ifndef traceMALLOC #define traceMALLOC( pvAddress, uiSize ) #end
最後にxTaskResumeAll()
を読もうと思ったけど、長いので次回に持ち越し。
従って、次回はvTaskStartScheduler() -> xTaskCreate() -> pvPortMalloc()
から呼ばれた `xTaskResumeAll()' から続きを読んでいきます。
今回はいろいろ積み残しがありました。