ESP-IDFを使ってみる

タスクのスケジューリング

esp-idfのexamplesディレクトには多数のサンプルソースが含まれていますが、
一番気になっていたマルチコアの環境で、タスクを複数同時に起動したときのスケジュール動作を確認してみました。

RTOSのタスクスケジュール・ルールは、原則優先度の高いタスクが先に終わりますが、
同じ優先度のタスクが複数、実行可能になった時の扱いがRoundRobin方式とFIFO方式で違います。

ESP-IDFが採用しているFreeRTOSはRoundRobin方式を採用しています。
RoundRobin方式というのは、1回あたりに使っていい時間を決めて、その時間が来たら次に使いたい人に譲りましょうというルールです。
FIFO方式というのは、「First In First Out」の略で、先に来たやつから片付けるという、簡単に行ってしまえば早い者勝ちのルールです。

テストで使用したのは以下のコードです。
条件を色々変えて確認してみました。
ConsumptionTick()は指定したTick数、時間稼ぎをする関数です。
開始時点のコア番号を渡すと、途中でコア番号が変化した回数を数えます。
/* The example of esp-idf
 *
 * This sample code is in the public domain.
 */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

//時間稼ぎ
//コア番号が変化した回数を戻す
int ConsumptionTick(int delay, int startID) {
    TickType_t startTick;
    TickType_t endTick;
    TickType_t nowTick;
    startTick = xTaskGetTickCount();
    endTick = startTick + delay;
    int _currentID = startID;
    int _swCount = 0;
    int _nowID;

    while(1) {
      __asm__  volatile (
        "rsr.prid %0\n"
        :"=r"(_nowID));
      if (_currentID != _nowID) {
        _swCount++;
        _currentID = _nowID;
      }
      nowTick = xTaskGetTickCount();
      if (nowTick > endTick) break;
    }
    return _swCount;
}

void task(void *pvParameter)
{
    int CoreId;
    int RealId;
    char * my_name = pcTaskGetTaskName( NULL );
    UBaseType_t my_prio = uxTaskPriorityGet( NULL );

    CoreId = xPortGetCoreID();
    __asm__  volatile (
        "rsr.prid %0\n"
        :"=r"(RealId));

    ESP_LOGI(my_name, "start RealId=%d CoreId=%d Priority=%d",RealId, CoreId, my_prio);
    ConsumptionTick(200,
CoreId);
    CoreId = xPortGetCoreID();
    __asm__  volatile (
        "rsr.prid %0\n"
        :"=r"(RealId));

    ESP_LOGI(my_name, "end RealId=%d CoreId=%d Priority=%d",RealId, CoreId, my_prio);
    vTaskDelete( NULL );
}

void app_main()
{
    char * my_name = pcTaskGetName(NULL);
    int CoreId = xPortGetCoreID();
    UBaseType_t my_prio = uxTaskPriorityGet( NULL );
    int RealId;
    __asm__  volatile (
        "rsr.prid %0\n"
        :"=r"(RealId));


    ESP_LOGI(my_name, "start RealId=%d CoreId=%d Priority=%d",RealId,CoreId,my_prio);
    ESP_LOGI(my_name, "portTICK_PERIOD_MS=%d[ms]",portTICK_PERIOD_MS);
    ESP_LOGI(my_name, "configMAX_PRIORITIES=%d",configMAX_PRIORITIES);
    ESP_LOGI(my_name, "configMINIMAL_STACK_SIZE=%d",configMINIMAL_STACK_SIZE);

#if 0
// Task1を優先度=1で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
#endif

#if 0
// Task1を優先度=1で起動
// Task2を優先度=1で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task2", 2048, NULL, 1, NULL);
#endif

#if 0
// Task1を優先度=1で起動
// Task2を優先度=1で起動
// Task3を優先度=1で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task2", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task3", 2048, NULL, 1, NULL);
#endif

#if 0
// Task1を優先度=1で起動
// Task2を優先度=1で起動
// Task3を優先度=1で起動
// Task4を優先度=1で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task2", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task3", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task4", 2048, NULL, 1, NULL);
#endif

#if 0
// Task1を優先度=1で起動
// Task2を優先度=2で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task2", 2048, NULL, 2, NULL);
#endif

#if 0
// Task1を優先度=1で起動
// Task2を優先度=2で起動
// Task3を優先度=3で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task2", 2048, NULL, 2, NULL);
    xTaskCreate(&task, "Task3", 2048, NULL, 3, NULL);
#endif

#if 0
// Task1を優先度=1で起動
// Task2を優先度=2で起動
// Task3を優先度=3で起動
// Task4を優先度=4で起動
    xTaskCreate(&task, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(&task, "Task2", 2048, NULL, 2, NULL);
    xTaskCreate(&task, "Task3", 2048, NULL, 3, NULL);
    xTaskCreate(&task, "Task4", 2048, NULL, 4, NULL);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=1,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 1, NULL, 1);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=1,Core1で起動
// Task3を優先度=1,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task3", 2048, NULL, 1, NULL, 1);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=1,Core1で起動
// Task3を優先度=1,Core1で起動
// Task4を優先度=1,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task3", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task4", 2048, NULL, 1, NULL, 1);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=2,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 2, NULL, 1);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=2,Core1で起動
// Task3を優先度=3,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 2, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task3", 2048, NULL, 3, NULL, 1);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=2,Core1で起動
// Task3を優先度=3,Core1で起動
// Task4を優先度=4,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 2, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task3", 2048, NULL, 3, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task4", 2048, NULL, 4, NULL, 1);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=2,Core0で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 2, NULL, 0);
#endif

#if 0
// Task1を優先度=1,Core1で起動
// Task2を優先度=2,Core0で起動
// Task2を優先度=3,Core1で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 2, NULL, 0);
    xTaskCreatePinnedToCore(&task, "Task3", 2048, NULL, 3, NULL, 1);
#endif

#if 1
// Task1を優先度=1,Core0で起動
// Task2を優先度=2,Core1で起動
// Task2を優先度=3,Core0で起動
    xTaskCreatePinnedToCore(&task, "Task1", 2048, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(&task, "Task2", 2048, NULL, 2, NULL, 1);
    xTaskCreatePinnedToCore(&task, "Task3", 2048, NULL, 3, NULL, 0);
#endif


}



まずはxTaskCreate()を使って同じ優先度のタスクをいくつか起動してみました。
起動するタスクが1つの場合、結果は以下の様になりました。
Task1はCore#1で最初から最後まで動きました。
I (239) main: start RealID=52685 CoreID=0 Priority=1
I (239) main: portTICK_PERIOD_MS=10[ms]
I (239) main: configMAX_PRIORITIES=25
I (249) main: configMINIMAL_STACK_SIZE=768
I (249) Task1: start RealID=43947 CoreID=1 Priority=1
I (2269) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが2つの場合、面白いことが起こります。
タスクの優先度は同じなので各タスクはRoundRobinで平等に動きます。
Task1はCore#1、Task2はCore#0で動き始めますが、Task1は実行中にCore#1→Core#0→Core#1とコアが 入れ替わります。
xTaskCreate()でタスクを起動すると、ESP32のFreeRTOSのルールに従ってコアを決めますが、
途中でどちらかのコアが空くと、コアのアサインを動的に変更します。
以前のバージョンのxPortGetCoreID()にはバグが有り、常にタスク開始時のコア番号を戻しましたが、最新バージョンでは修正されて います。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task2: start RealID=52685 CoreID=0 Priority=1
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (2305) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=2
I (2315) Task2: end RealID=52685 CoreID=0 Priority=1 swCount=0

起動するタスクが3つの場合、2つのコアを3つのタスクで使うので、コアの割り付けはもっと頻繁に変わります。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (285) Task2: start RealID=52685 CoreID=0 Priority=1
I (285) Task3: start RealID=52685 CoreID=0 Priority=1
I (2305) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=134
I (2315) Task3: end RealID=43947 CoreID=1 Priority=1 swCount=133
I (2315) Task2: end RealID=52685 CoreID=0 Priority=1 swCount=134

起動するタスクが4つの場合、2つのコアを4つのタスクで交互に使うので、使うコアは大体同じコアになります。
下記の例では、各タスクのコアは以下のように変わったことが分かります。
Task1 Core#1→Core#0→Core#1
Task2 Core#0→Core#1→Core#0→Core#1(終了直前でコアが変わっています)
Task3 Core#0→Core#1→Core#0
Task4 Core#0→Core#1
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (285) Task2: start RealID=52685 CoreID=0 Priority=1
I (285) Task4: start RealID=52685 CoreID=0 Priority=1
I (285) Task3: start RealID=52685 CoreID=0 Priority=1
I (2305) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=2
I (2315) Task2: end RealID=43947 CoreID=1 Priority=1 swCount=2
I (2325) Task4: end RealID=43947 CoreID=1 Priority=1 swCount=1
I (2325) Task3: end RealID=52685 CoreID=0 Priority=1 swCount=2

奇数個のタスクを同時に動かした場合、2つのコアを各タスクが交互に使うので、各タスクが使用するコアは頻繁に変わります。



次に、xTaskCreate()を使って起動するタスクの優先度を変えてみました。
起動するタスクが2つの場合、結果は以下の様になりました。
Task2はCore#1で動き始めますが、途中でCore#0が空いたので、Core#0に1回だけスイッチします。
2つのコアを同時に使うので、優先度の低いタスクも並行して動くことができます。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
I (2305) Task2: end RealID=52685 CoreID=0 Priority=2 swCount=1
I (2315) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが3つの場合、結果は以下の様になりました。
Task2とTask3のコアが入れ替わります。
Task1は優先度が低く、どちらのコアも使えないので、どちらかのコアが空くまで待たされます。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
I (285) Task3: start RealID=52685 CoreID=0 Priority=3
I (2305) Task2: end RealID=52685 CoreID=0 Priority=2 swCount=1
I (2305) Task3: end RealID=43947 CoreID=1 Priority=3 swCount=1
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (4315) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが4つの場合、結果は以下の様になりました。
Task1とTask4のコアが1回だけスイッチします。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
I (285) Task4: start RealID=52685 CoreID=0 Priority=4
I (285) Task3: start RealID=52685 CoreID=0 Priority=3
I (2305) Task4: end RealID=43947 CoreID=1 Priority=4 swCount=1
I (2315) Task3: end RealID=52685 CoreID=0 Priority=3 swCount=0
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (4315) Task2: end RealID=43947 CoreID=1 Priority=2 swCount=0
I (4325) Task1: end RealID=52685 CoreID=0 Priority=1 swCount=1

xTaskCreate()を使うと特定のルールに従ってコア番号を決めます。

タスクの優先度が同じときは、各タスクはRoundRobinでスケジュールされるので、
(特にタスクの数が奇数の場合は頻繁に)使用するコアが変わります。

タスクの優先度が違うときは、優先度の高いタスクから実行されるので、
基本は同じコアに居続けますが、他のコアが空いた時は、
その時点で動いているタスクの中で、優先度の一番高いタスクが、
自動的に空きになったコアにスイッチするようです。

タスクの優先度を変えて複数のタスクを実行すると、必ず優先度の高いタスクから終わります。
RTOSの基本中の基本です。



次に、xTaskCreatePinnedToCore()を使って優先度の同じタスクを、同じコアでいくつか起動してみました。
起動するタスクが2つの場合、結果は以下の様になりました。
当然コアのスイッチは発生しません。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (295) Task2: start RealID=43947 CoreID=1 Priority=1
I (2305) Task2: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2315) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが3つの場合、結果は以下の様になりました。
xTaskCreate()で3つのタスクを起動したときは、2315Tickで終わりましたが、それよりも、10Tick遅く全てのタスクが終 わります。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (295) Task3: start RealID=43947 CoreID=1 Priority=1
I (295) Task2: start RealID=43947 CoreID=1 Priority=1
I (2305) Task3: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2315) Task2: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2325) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが4つの場合、結果は以下の様になりました。
xTaskCreate()で4つのタスクを起動したときは、2325Tickで終わりましたが、それよりも、全てのタスクの終了はかなり遅くな ります。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (295) Task3: start RealID=43947 CoreID=1 Priority=1
I (295) Task2: start RealID=43947 CoreID=1 Priority=1
I (295) Task4: start RealID=43947 CoreID=1 Priority=1
I (2315) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2345) Task3: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2355) Task2: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2355) Task4: end RealID=43947 CoreID=1 Priority=1 swCount=0

いずれの場合も、コアのスイッチは発生しません。
1つのコアを複数のタスクが入れ替わり使うので、パフォーマンスが悪くなります。
やっちゃダメなパターンです。



次に、xTaskCreatePinnedToCore()を使って優先度の違うタスクを、同じコアでいくつか起動してみました。
起動するタスクが2つの場合、結果は以下の様になりました。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
I (2305) Task2: end RealID=43947 CoreID=1 Priority=2 swCount=0
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (4315) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが3つの場合、結果は以下の様になりました。
Task1がいつまでたっても動けないので、watchdog関係のエラーが発生しています。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task3: start RealID=43947 CoreID=1 Priority=3
I (2305) Task3: end RealID=43947 CoreID=1 Priority=3 swCount=0
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
I (4315) Task2: end RealID=43947 CoreID=1 Priority=2 swCount=0
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
E (5285) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (5285) task_wdt:  - IDLE1 (CPU 1)
E (5285) task_wdt: Tasks currently running:
E (5285) task_wdt: CPU 0: IDLE0
E (5285) task_wdt: CPU 1: Task1
I (6325) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが4つの場合、結果は以下の様になりました。
今度は、Task2がいつまでたっても動けないので、watchdog関係のエラーが発生しています。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task4: start RealID=43947 CoreID=1 Priority=4
I (2305) Task4: end RealID=43947 CoreID=1 Priority=4 swCount=0
I (285) Task3: start RealID=43947 CoreID=1 Priority=3
I (4315) Task3: end RealID=43947 CoreID=1 Priority=3 swCount=0
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
E (5285) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (5285) task_wdt:  - IDLE1 (CPU 1)
E (5285) task_wdt: Tasks currently running:
E (5285) task_wdt: CPU 0: IDLE0
E (5285) task_wdt: CPU 1: Task2
I (6325) Task2: end RealID=43947 CoreID=1 Priority=2 swCount=0
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (8335) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

TaskCreatePinnedToCore()で起動すると、他のコアが空いても、そちらへのスイッチは行いません。
これもやっちゃダメなパターンです。



最後に、xTaskCreatePinnedToCore()を使って優先度の違うタスクを、違うコアでいくつか起動してみました。
起動するタスクが2つの場合、結果は以下の様になりました。
Task1とTask2は使用するコアが違うので、並行して動くことができます。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task2: start RealID=52685 CoreID=0 Priority=2
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (2305) Task2: end RealID=52685 CoreID=0 Priority=2 swCount=0
I (2305) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0

起動するタスクが3つの場合、使うコアで結果が変わります。
Task1 優先度=1 コア=1
Task2 優先度=2 コア=0
Task3 優先度=3 コア=1

Task2が動き始めると、同じコアで動いているmainタスクは優先度が低いので実行権を奪われます。
Task2が終了すると、再びmainタスクが動き始め、Task3を生成します。
Task3は一番優先度が高いのに、Task2よりも遅くなってしまいます。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task2: start RealID=52685 CoreID=0 Priority=2
I (285) Task1: start RealID=43947 CoreID=1 Priority=1
I (2305) Task2: end RealID=52685 CoreID=0 Priority=2 swCount=0
I (2305) Task1: end RealID=43947 CoreID=1 Priority=1 swCount=0
I (2305) Task3: start RealID=43947 CoreID=1 Priority=3
I (4315) Task3: end RealID=43947 CoreID=1 Priority=3 swCount=0

コアの指定を変えてみました。
Task1 優先度=1 コア=0
Task2 優先度=2 コア=1
Task3 優先度=3 コア=0

Task1とmainタスクは同じコアですが、同じ優先度なので、並行して動くことができます。
Task2とmainタスクは違うコアで動くので、mainタスクは動き続けることができます。
Task3がスケジュールされ、Task1よりも優先度が高いのでTask3が動き始めます。
I (275) main: start RealID=52685 CoreID=0 Priority=1
I (275) main: portTICK_PERIOD_MS=10[ms]
I (275) main: configMAX_PRIORITIES=25
I (285) main: configMINIMAL_STACK_SIZE=768
I (285) Task3: start RealID=52685 CoreID=0 Priority=3
I (285) Task2: start RealID=43947 CoreID=1 Priority=2
I (2305) Task3: end RealID=52685 CoreID=0 Priority=3 swCount=0
I (2305) Task2: end RealID=43947 CoreID=1 Priority=2 swCount=0
I (2305) Task1: start RealID=52685 CoreID=0 Priority=1
I (4315) Task1: end RealID=52685 CoreID=0 Priority=1 swCount=0

このように、xTaskCreatePinnedToCore()では、タスクの逆転など思わぬ不具合の原因になったり、
全体のパフォーマンスが悪くなったりするので、特別の理由がない限り使わない方がいいです。
はっきり言って、マルチコアの環境では xTaskCreatePinnedToCore() は使ってはいけません

2台のエレベータが有る場合、普通はどちらか早く来た方に乗り込みます。
2つのコアが有るのに、コアを決め打ちしてタスクを始めるのは、2台のエレベータが有るのに、片方のエレベータだけをひたすら待ち続けるようなも のです。



起動するタスク数を変えて、10秒間(1000Ticks)でハノイの塔20段を何回終了できるかを実行してみました。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

//ハノイの塔の関数。負荷をかけるためだけのもので本質ではない。
void hanoi(int n,char a,char b,char c)
{
  if(n>0) {
    hanoi(n-1,a,c,b);
    hanoi(n-1,c,b,a);
  }
}

void task(void* param) {
  ESP_LOGI(pcTaskGetName(NULL), "Start on %d", xPortGetCoreID());
  int cnt=0;
  TickType_t startTick = xTaskGetTickCount();
  int n=20;
  while(1){
  int n=20;
  while(1){
    vTaskDelay(1);
    TickType_t currentTick = xTaskGetTickCount();
    if (currentTick - startTick < 1000) {
      hanoi(n,'a','b','c');
      cnt++;
    }else{
      ESP_LOGI(pcTaskGetName(NULL), "%d hanoi loops done",cnt);
      ESP_LOGI(pcTaskGetName(NULL), "%d ticks passed",currentTick-startTick);
      break;
    }
  }

  while(1){
    vTaskDelay(1);
  }
}

void app_main()
{
  xTaskCreate(task, "Task0", 2*1024, NULL, 5, NULL);
  xTaskCreate(task, "Task1", 2*1024, NULL, 5, NULL);
  xTaskCreate(task, "Task2", 2*1024, NULL, 5, NULL);
}

こちらがタスク数が1の時の結果です。
I (20393) Task0: 59 hanoi loops done
I (20393) Task0: 1004 ticks passed

こちらがタスク数が2の時の結果です。
2つのコアで、2つのタスクが同時に動いていることが分かります。
I (10478) Task0: 58 hanoi loops done
I (10478) Task0: 1015 ticks passed
I (10488) Task1: 58 hanoi loops done
I (10488) Task1: 1017 ticks passed

こちらがタスク数が3の時の結果です。
2つのコアを、3つのタスクが奪い合って動きます。
I (10418) Task2: 40 hanoi loops done
I (10418) Task2: 1009 ticks passed
I (10478) Task0: 40 hanoi loops done
I (10478) Task0: 1015 ticks passed
I (10508) Task1: 40 hanoi loops done
I (10508) Task1: 1018 ticks passed



マルチコア、マルチタスクが最も効果を発揮するのは、通信が関係するアプリケーションです。
こちらがシングルコア、シングルタスクの場合の動作です。
分かり易くするために、わざとデータ受信の処理時間と、データ演算の処理時間を同じにしています。
コア#1 データ1受信 データ1演算 データ2受信 データ2演算 データ3受信 データ3演算

こちらがデュアルコア、デュアルタスクの場合の動作です。
シングルコアの時に比べて、同じ時間でほぼ倍のデータを処理することができます。
コア#1 データ1受信 データ2受信 データ3受信 データ4受信 データ5受信 データ6受信
コア#2
データ1演算 データ2演算 データ3演算 データ4演算 データ5演算 データ6演算

こちらがシングルコア(あるいはコアを決め打ち)、デュアルタスクの場合の動作です。
1つのコアを2つのタスクが奪い合うので、処理は遅くなります。
コア#1 データ1受信       データ2受 信             データ3受 信             データ4受 信             データ5受 信             データ6受 信      
コア#1
      データ1演 算      
      データ2演 算             データ3演 算             データ4演 算             データ5演 算       データ6演算

データを送信する場合も同じです。
こちらがシングルコア、シングルタスクの場合の動作です。
コア#1 データ1準備 データ1送信 データ2準備 データ2送信 データ3準備 データ3送信

こちらがデュアルコア、デュアルタスクの場合の動作です。
コア#1 データ1準備 データ2準備 データ3準備 データ4準備 データ5準備 データ6準備
コア#2
データ1送信 データ2送信 データ3送信 データ4送信 データ5送信 データ6送信



こ ちら
にesp-idfでのタスクスケジュールの詳しい説明が有ります。
CPU0はProtocol CPU、CPU1はApplication CPUと呼ばれていて、それぞれで独立したタスクスケジューラーが動きます。
[Illustration of FreeRTOS Ready Task List Data Structure in ESP-IDF]の図が分かりやすいです。

xTaskCreatePinnedToCore()ではコアの指定に、「0/1/tskNO_AFFINITY」のいずれかを使うことができま す。
xTaskCreate()は以下の様に「tskNO_AFFINITY」を引数として、xTaskCreatePinnedToCore()を 呼び出しています。

    static inline IRAM_ATTR BaseType_t xTaskCreate(
            TaskFunction_t pvTaskCode,
            const char * const pcName,
            const uint32_t usStackDepth,
            void * const pvParameters,
            UBaseType_t uxPriority,
            TaskHandle_t * const pvCreatedTask)
    {
        return xTaskCreatePinnedToCore( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask, tskNO_AFFINITY );
    }




ESP-IDFでは様々なシステムタスク(Built-In Task)が裏で動きます。
こ ちらにBuilt-In Taskの優先度の説明が有ります。
さらに、同じ資料にアプリケーション タスクの優先度の選択の解説が有ります。
アプリケーションが1つのタスクであれば、全く考慮する必要は有りませんが、複数のタスクで構成される場合や、
TCP/IP(ネットワーク)を使う場合は、一読をお勧めします。
また、SPIFFSなどの内蔵ファイルシステムを使う場合、ファイルへの書き込み中はタスクの実行はSuspendedになると書かれています。
内蔵ファイルシステムを使う場合も、タスクの優先度には注意が必要です。

続く....