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;
}

メモリをヒープから確保する。下記順に実行されている

  1. タスクを全部サスペンドする(vTaskSuspendAll()
  2. malloc してヒープからメモリをxWantedSize [byte] 確保する。
  3. サスペンドしたタスクをレジュームする(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)で扱われているから問題ないね。 と納得しているのでなんかそういうことなんだと思うけど、あとでもう一度考えてみます。

  • レジスタ値をインクリメントする

  • レジスタ値を (メモリ上の)uxSchedulerSuspendeded にストアする 他のタスクにこの変数が使用されたとしても、uxSchedulerSuspendeded は正しい値である1になる。

malloc() は stdlib.h で宣言されている。malloc実装はあとでみる。 mallocを解説した動画www.youtube.comも参考になる。

traceMALLOC() は、traceMALLOC が定義されておらずArduino UNO版では実態はないようだ。

#ifndef traceMALLOC
    #define traceMALLOC( pvAddress, uiSize )
#end

最後にxTaskResumeAll() を読もうと思ったけど、長いので次回に持ち越し。

従って、次回はvTaskStartScheduler() -> xTaskCreate() -> pvPortMalloc() から呼ばれた `xTaskResumeAll()' から続きを読んでいきます。 今回はいろいろ積み残しがありました。