ESP-IDFを使ってみる

タスク状態の監視

こちらでesp-open-rtosのタスクの監視を 紹介していますが、esp-idfでは少し挙動が違います。
まずは、esp-open-rtosと同じコードを試してみます。
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "MAIN";

static void test_task(void *pvParameters)
{
    ESP_LOGI(pcTaskGetTaskName(0), "Start");
    for(int i=0;i<5;i++) {
        vTaskDelay(100);
    }
    ESP_LOGI(pcTaskGetTaskName(0), "Finish");
    vTaskDelete(NULL);
}


void app_main(void)
{
    TaskHandle_t xHandle = NULL;
    xTaskCreate(&test_task, "TEST", 8192, NULL, 3, &xHandle);
    while(1) {
        eTaskState taskState = eTaskGetState( xHandle );
        ESP_LOGI(TAG, "taskState=%d",taskState);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

TESTタスクが終了すると、一瞬タスク状態はeDeletedになりますが、直ぐにeReadyに変わります。
I (304) MAIN: taskState=1
I (304) TEST: Start
I (1314) MAIN: taskState=2
I (2314) MAIN: taskState=2
I (3314) MAIN: taskState=2
I (4314) MAIN: taskState=2
I (5314) TEST: Finish
I (5314) MAIN: taskState=4
I (6314) MAIN: taskState=1
I (7314) MAIN: taskState=1
I (8314) MAIN: taskState=1
I (9314) MAIN: taskState=1

EspressIFに確認したら以下の回答でした。
Checking task status after a task is deleted might be valid for a task with statically allocated TCB.

But in your case the TCB is originally allocated from the heap, so the memory is released back to the heap when the task is deleted.
By passing the task handle (which in FreeRTOS is a pointer to TCB) to the eTaskGetState function, you are essentially causing a "use after free", reading some value from a now-deallocated heap block.

This behavior is the same in upstream FreeRTOS.

Google翻訳君にお願いします。
タスクが削除された後のタスクステータスのチェックは、TCBが静的に割り当てられ たタスクに対して有効な場合があります。

しかし、あなたの場合、TCBはもともとヒープから割り当てられているため、タスクが削除されると、メモリはヒープに解放さ れます。
タスクハンドル(FreeRTOSではTCBへのポインター)をeTaskGetState関数に渡すことにより、 本質的に「解放後使用」を引き起こし、現在割り当て解除されているヒープブロックから値を読み取ります。

この動作は、上流のFreeRTOSでも同じです。

要するにvTaskDelete()でdeleteしたタスクのタスクハンドルは無効になるので、正しくタスク状態を取ることができないみたいで す。



そこで、以下のコードで試してみました。
このコードをビルドするためには、menuconfigでCompoent config→FreeRTOS→Kernel→configUSE_TRACE_FACILITYを有効にする必要が有ります。
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "MAIN";

static void test_task(void *pvParameters)
{
        ESP_LOGI(pcTaskGetTaskName(0), "Start");
        for(int i=0;i<5;i++) {
                vTaskDelay(100);
        }
        ESP_LOGI(pcTaskGetTaskName(0), "Finish");
        vTaskDelete(NULL);
}


void app_main(void)
{
    TaskHandle_t xHandle = NULL;
    xTaskCreate(&test_task, "TEST", 8192, NULL, 3, &xHandle);

    UBaseType_t uxArraySize = uxTaskGetNumberOfTasks();
    ESP_LOGI(TAG, "uxArraySize=%d", uxArraySize);
    TaskStatus_t *pxTaskStatusArray;
    pxTaskStatusArray = pvPortMalloc( uxArraySize * sizeof( TaskStatus_t ) );
    if( pxTaskStatusArray == NULL ) {
        ESP_LOGE(TAG, "pvPortMalloc fail");
    }
    while(1) {
        eTaskState taskState = eTaskGetState( xHandle );
        ESP_LOGI(TAG, "taskState=%d",taskState);

        /* Generate raw status information about each task. */
        uint32_t ulTotalRunTime;
        uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalRunTime );
        for( int x = 0; x < uxArraySize; x++ ) {
            ESP_LOGI(TAG, "pcTaskName=%s eCurrentState=%d xTaskNumber=%lu, uxCurrentPriority=%lu",
                pxTaskStatusArray[ x ].pcTaskName,
                pxTaskStatusArray[ x ].eCurrentState,
                (unsigned long)pxTaskStatusArray[ x ].xTaskNumber,
                (unsigned long)pxTaskStatusArray[ x ].uxCurrentPriority);
        }

        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }

}

5313TickでTESTタスクが終了すると、TCB(Task Control Block)からこのタスクのエントリが消えます。
I (303) MAIN: uxArraySize=9
I (303) TEST: Start
I (313) MAIN: taskState=2
I (313) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (323) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (333) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (333) MAIN: pcTaskName=TEST eCurrentState=2 xTaskNumber=12, uxCurrentPriority=3
I (343) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (353) MAIN: pcTaskName=dport eCurrentState=4 xTaskNumber=4, uxCurrentPriority=5
I (363) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (373) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (383) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (1383) MAIN: taskState=2
I (1383) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (1383) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (1383) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (1393) MAIN: pcTaskName=TEST eCurrentState=2 xTaskNumber=12, uxCurrentPriority=3
I (1403) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (1413) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (1423) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (1433) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (2433) MAIN: taskState=2
I (2433) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (2433) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (2433) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (2443) MAIN: pcTaskName=TEST eCurrentState=2 xTaskNumber=12, uxCurrentPriority=3
I (2453) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (2463) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (2473) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (2483) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (3483) MAIN: taskState=2
I (3483) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (3483) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (3483) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (3493) MAIN: pcTaskName=TEST eCurrentState=2 xTaskNumber=12, uxCurrentPriority=3
I (3503) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (3513) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (3523) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (3533) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (4533) MAIN: taskState=2
I (4533) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (4533) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (4533) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (4543) MAIN: pcTaskName=TEST eCurrentState=2 xTaskNumber=12, uxCurrentPriority=3
I (4553) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (4563) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (4573) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (4583) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (5313) TEST: Finish
I (5583) MAIN: taskState=1
I (5583) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (5583) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (5583) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (5593) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (5603) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (5613) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (5623) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (6633) MAIN: taskState=1
I (6633) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (6633) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (6633) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (6643) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (6653) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22
I (6663) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (6673) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (7683) MAIN: taskState=1
I (7683) MAIN: pcTaskName=main eCurrentState=1 xTaskNumber=5, uxCurrentPriority=1
I (7683) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=7, uxCurrentPriority=0
I (7683) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (7693) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=8, uxCurrentPriority=1
I (7703) MAIN: pcTaskName=ipc1 eCurrentState=2 xTaskNumber=3, uxCurrentPriority=24
I (7713) MAIN: pcTaskName=ipc0 eCurrentState=2 xTaskNumber=2, uxCurrentPriority=24
I (7723) MAIN: pcTaskName=esp_timer eCurrentState=2 xTaskNumber=1, uxCurrentPriority=22

EspressIFのエンジニアは vTaskSetThreadLocalStoragePointerAndDelCallback() のAPIを使えば、
vTaskDelete()イベントを取れると教えてくれましたがまだ試していません。
FreeRTOSの関数を使って自前でタスクの終了を通知するようにした方が簡単です。



これがWiFiを使わないときのタスクの一覧です。
esp_timer、Tmr Svc、ipc0、ipc1のタスクが常駐します。
I (17147) MAIN: pcTaskName=main eCurrentState=0 xTaskNumber=4, uxCurrentPriority=1
I (17147) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (17147) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=5, uxCurrentPriority=0
I (17157) MAIN: pcTaskName=esp_timer eCurrentState=3 xTaskNumber=3, uxCurrentPriority=22
I (17167) MAIN: pcTaskName=ipc1 eCurrentState=3 xTaskNumber=2, uxCurrentPriority=24
I (17177) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=7, uxCurrentPriority=1
I (17187) MAIN: pcTaskName=ipc0 eCurrentState=3 xTaskNumber=1, uxCurrentPriority=24

これがWiFiを有効にした時のタスクの一覧です。
WiFiを有効にすると、さらにtiT、wifi、sys_evtのタスクが追加で常駐します。
これはStationモードでもSoftAPモードでも同じです。
tiTはTCP-IPスタック(LwIP)のメインタスクで、TCP-IP パケットを処理するタスクです。
sys_evtは(おそらく)Wi-Fi や TCP-IP スタック イベントなどのシステム イベントを処理するタスクです。
SoftAPモードの時はDHCP serverが有効になりますが、タスクではなくtiTのモジュールとして動作するみたいです。
I (6434) MAIN: pcTaskName=main eCurrentState=0 xTaskNumber=4, uxCurrentPriority=1
I (6434) MAIN: pcTaskName=IDLE1 eCurrentState=1 xTaskNumber=6, uxCurrentPriority=0
I (6444) MAIN: pcTaskName=IDLE0 eCurrentState=1 xTaskNumber=5, uxCurrentPriority=0
I (6454) MAIN: pcTaskName=tiT eCurrentState=2 xTaskNumber=8, uxCurrentPriority=18
I (6464) MAIN: pcTaskName=Tmr Svc eCurrentState=2 xTaskNumber=7, uxCurrentPriority=1
I (6474) MAIN: pcTaskName=ipc1 eCurrentState=3 xTaskNumber=2, uxCurrentPriority=24
I (6474) MAIN: pcTaskName=esp_timer eCurrentState=3 xTaskNumber=3, uxCurrentPriority=22
I (6484) MAIN: pcTaskName=wifi eCurrentState=2 xTaskNumber=10, uxCurrentPriority=23
I (6494) MAIN: pcTaskName=sys_evt eCurrentState=2 xTaskNumber=9, uxCurrentPriority=20
I (6504) MAIN: pcTaskName=ipc0 eCurrentState=3 xTaskNumber=1, uxCurrentPriority=24

vTaskList()でフォーマットされたタスクの状態を取り出すことができます。
左から、タスク名、タスク状態、タスク優先度、実行中のコア番号、現在のスタックサイズ、タスク番号です。
tiTタスクのコア番号の表示が正しくないです。
main            X       1       0       1004    4
IDLE1           R       0       1       1028    6
IDLE0           R       0       0       920     5
tiT             B       18      2147483647      2100    8
ipc1            S       24      1       460     2
esp_timer       S       22      0       3312    3
wifi            B       23      0       908     10
sys_evt         B       20      0       836     9
ipc0            S       24      0       480     1
Tmr Svc         B       1       0       1504    7

ESP-IDFをv5.2.1-210→v5.2.1-533にアップデートしたら、項目の並びがV4時代の並びに戻りました。
左から、タスク名、タスク状態、タスク優先度、現在のスタックサイズ、タスク番号、実行中のコア番号です。
また、tiTタスクのコア番号の表示が正しくなりました。
tiTタスクのコア番号は-1(Not pinned)になっています。
これはtiTタスクが動くコアが固定されていないことを意味しています。
tiTタスクの優先度は結構高いので、WiFiを有効にするだけで、コア#1で動いているタスクは影響を受けます。
main            X       1       1004    4       0
IDLE1           R       0       916     6       1
IDLE0           R       0       920     5       0
tiT             B       18      2104    8       -1
ipc0            S       24      480     1       0
esp_timer       S       22      3312    3       0
Tmr Svc         B       1       1504    7       0
wifi            B       23      656     10      0
ipc1            S       24      492     2       1
sys_evt         B       20      840     9       0



タスクの終了を通知する方法は幾つか有ります。
タスクの結果を文字列などで起動元に戻すときはQueueやRingBufferを使います。
タスクの結果を24Bitの数値で起動元に戻すときはEventGroupを使います。
ただ単に、タスクが終わったことを起動元に通知するときにはTask Notifications機能を使います。
MutexやSemaphoreを使って、タスクの終了を通知することもできますが、これらはどちらかというと、資源の排他制御のために使われま す。

以下はxTaskNotifyGiveを使った例です。
子タスク起動時に親タスクのタスクハンドルをパラメータで渡しています。
子タスクは終了時に xTaskNotifyGive で親タスクに終了を通知します。
親タスクは ulTaskNotifyTake で子タスクの終了を待ちます。
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

void task(void *pvParameters)
{
    TaskHandle_t ParentTaskHandle = (TaskHandle_t)pvParameters;
    ESP_LOGI(pcTaskGetName(NULL), "START tick:%"PRIu32, xTaskGetTickCount());
    vTaskDelay(500);
    xTaskNotifyGive( ParentTaskHandle );
    //xTaskNotify( ParentTaskHandle, 0x10, eSetValueWithOverwrite );
    ESP_LOGI(pcTaskGetName(NULL), "END tick:%"PRIu32, xTaskGetTickCount());
    vTaskDelete( NULL );
}

void app_main()
{
    TaskHandle_t ParentTaskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t my_prio = uxTaskPriorityGet(NULL);
    ESP_LOGI(pcTaskGetName(NULL), "my_prio=%d", my_prio);

    xTaskCreate(&task, "Task", 2048, (void *)ParentTaskHandle, my_prio+1, NULL);
    uint32_t value = ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
    ESP_LOGI(pcTaskGetName(NULL), "ulTaskNotifyTake value=0x%"PRIx32, value);
}

実行すると以下の様にmain側の ulTaskNotifyTake で子タスクの終了を検出できます。
ulTaskNotifyTakeの正常時の戻り値は1、タイムアウト発生時は0です。
I (303) main_task: Started on CPU0
I (313) main_task: Calling app_main()
I (313) main: my_prio=1
I (313) Task: START tick:1
I (5313) Task: END tick:501
I (5313) main: ulTaskNotifyTake value=0x1
I (5313) main_task: Returned from app_main()



xTaskNotifyGiveの代わりに、xTaskNotifyを使う方法も有ります。
xTaskNotifyでは32Bitの戻り値を設定することができます。
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

void task(void *pvParameters)
{
    TaskHandle_t ParentTaskHandle = (TaskHandle_t)pvParameters;
    ESP_LOGI(pcTaskGetName(NULL), "START tick:%"PRIu32, xTaskGetTickCount());
    vTaskDelay(500);
    //xTaskNotifyGive( ParentTaskHandle );
    xTaskNotify( ParentTaskHandle, 0x10, eSetValueWithOverwrite );
    ESP_LOGI(pcTaskGetName(NULL), "END tick:%"PRIu32, xTaskGetTickCount());
    vTaskDelete( NULL );
}

void app_main()
{
    TaskHandle_t ParentTaskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t my_prio = uxTaskPriorityGet(NULL);
    ESP_LOGI(pcTaskGetName(NULL), "my_prio=%d", my_prio);

    xTaskCreate(&task, "Task", 2048, (void *)ParentTaskHandle, my_prio+1, NULL);
    uint32_t value = ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
    ESP_LOGI(pcTaskGetName(NULL), "ulTaskNotifyTake value=0x%"PRIx32, value);
}

実行すると以下の様にmain側の ulTaskNotifyTake で子タスクの終了と終了コードを検出できます。
タイムアウト発生時の終了コードは0なので、終了コードに0は使えません
xQueueOverwrite()の軽量な代替手段として使用することができます。
I (302) main_task: Started on CPU0
I (312) main_task: Calling app_main()
I (312) main: my_prio=1
I (312) Task: START tick:1
I (5312) Task: END tick:501
I (5312) main: ulTaskNotifyTake value=0x10
I (5312) main_task: Returned from app_main()



xTaskNotifyGiveの代わりに、vTaskResumeを使う方法も有ります。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

void task(void *pvParameters)
{
    TaskHandle_t taskHandle = (TaskHandle_t)pvParameters;
    ESP_LOGI(pcTaskGetName(NULL), "START tick:%"PRIu32, xTaskGetTickCount());
    vTaskDelay(500);
    //xTaskNotifyGive(taskHandle);
    vTaskResume(taskHandle);
    ESP_LOGI(pcTaskGetName(NULL), "END tick:%"PRIu32, xTaskGetTickCount());
    vTaskDelete( NULL );
}

void app_main()
{
    TaskHandle_t taskHandle = xTaskGetCurrentTaskHandle();
    UBaseType_t my_prio = uxTaskPriorityGet(NULL);
    ESP_LOGI(pcTaskGetName(NULL), "my_prio=%d", my_prio);

    xTaskCreate(&task, "Task", 2048, (void *)taskHandle, my_prio+1, NULL);
    //ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
    vTaskSuspend(NULL);
    ESP_LOGI(pcTaskGetName(NULL), "vTaskSuspend");
}

ulTaskNotifyTakeとvTaskSuspendの違いは、ulTaskNotifyTakeはタスク状態ををBlockedにし、
vTaskSuspendはタスク状態をSuspendedにします。
こ ちらにBlcokedとSuspendedの違いについて書かれた資料が有ります。

基本的な違いは、ブロックされた状態のタスクには常にタイムアウトがあることです。
中断状態のタスクは無期限に中断され、タイムアウトはありません。
portMAXDELAY のブロック時間を使用すると、タスクは無期限にブロックされますが、その状態は Suspended として報告されます。
一方、( portMAXDELAY - 1 ) のブロック時間を使用すると、タスクはその状態を Blocked として報告します。

と書かれています。
つまり、
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

vTaskSuspend(NULL);
は、どちらもタスク状態をSuspendedにします。



続く....