Open3

【Wio Terminal】Stack Overflow in FreeRTOS: stack pointer limit

amenaruyaamenaruya

Error Serial

これまで、FreeRTOSの停止するこそ夥多あれ、その実情を知る機会は殆ど無かった。唯一知る術であったのは、User LEDの点滅回数が示すエラーコードであった。此度、漸う第二の術を知ることができた。

汎用OSに於いても、標準出力の他にエラー出力というものが存在する。似たるもののありせば利便にも供せましと永らく不満に感じていたが、Error Serialというものがあることに漸く気が付いた。仕組みの如何はさて置き、これで少しはエラーの様子が分かるようになる。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/error_hooks.cpp#L24-L28

用例

次のプログラムは、小数値を表示することに起因して、決まって停止することが確かめられている。

error serial
#include "rpcWiFi.h"

#define STACK_SIZE 256

using namespace erpc;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    unsigned int flag = 10;

    //float flag2 = static_cast<float>(flag) / 3.3f;

    double flag3 = static_cast<double>(flag) / 3.3d;

    while (1) {
        Serial.println("Thread A\tHi");

        /*Serial.printf("Thread A\tflag: %u\n", flag);*/

        /*
        int printed;
        char string[24];
        printed = snprintf(
            string,
            24,
            "Thread A\tflag: %u\n",
            flag
        );
        if (printed >= 0)
            Serial.println(string);
        */

        Serial.print("Thread A\tflag: ");
        Serial.println(flag);

        /*
        Serial.print("Thread A\tfloat: ");
        Serial.println(flag2);
        */

        Serial.print("Thread A\tdouble: ");
        Serial.println(flag3);

        flag++;
        //flag2 = static_cast<float>(flag) / 3.3f;
        flag3 = static_cast<double>(flag) / 3.3d;

        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread B\tStarted");

    while (1) {
        for (int i = 0; i < 10; i++) {
            Serial.println("Thread B\tHello?");
            delay(2000);
        }
        Serial.println("Thread B\tloop");
    }
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);
    /* error output */
    vSetErrorSerial(&Serial);

    Thread TaskA(
        &ThreadA,
        configMAX_PRIORITIES - 10,
        STACK_SIZE,
        "Task A"
    );

    Thread TaskB(
        &ThreadB,
        configMAX_PRIORITIES - 11,
        STACK_SIZE,
        "Task B"
    );

    Thread* tasksArray[2] = {&TaskA, &TaskB};

    for (Thread* t : tasksArray) {
        t -> start();
    }
}

void loop() {}

serial monitor
Thread A	Started
Thread A	Hi
Thread A	flag: 10
Thread A	double: 3.03

Stack Overflow: Task A


ASSERT: vPortEnterCritical :#420

User LEDの点滅回数は1回である。

Stack Overflow

Stack Overflow: Task Aの表示は、vApplicationStackOverflowHook()に従っている。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/error_hooks.cpp#L137-L156

この発動箇所はここと見られる。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/FreeRTOS/Source/include/stack_macros.h#L46-L74

着目するのは玆。

/* Is the currently saved stack pointer within the stack limit? */                            \
if( pxCurrentTCB->pxTopOfStack >= pxCurrentTCB->pxEndOfStack )                                \
{                                                                                             \
    vApplicationStackOverflowHook( ( TaskHandle_t ) pxCurrentTCB, pxCurrentTCB->pcTaskName ); \
} 

コメントの内容から、stack pointerの上限に達するか否かが確かめられていると見える。

ASSERT

ASSERT: vPortEnterCritical :#420の表示は、configASSERT()に従っており、ASSERT: 呼び出し元関数名 :#行数を表すものと思われる。この定義は複数存在する。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/arch/samd21/FreeRTOSConfig.h#L88-L93

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/arch/samd51/FreeRTOSConfig.h#L100-L105

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/arch/stm32h7xx/FreeRTOSConfig.h#L89-L94

ここでassertBlink()とは、一回の点滅を繰り返すものであり、今回の点滅回数と一致する。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/FreeRTOSVariant.c#L21-L27

コメントにある通り、configASSERTに失敗したことを示している。

🤔🤔🤔

また、vPortEnterCritical()も複数存在するが、420行目にconfigASSERT()を実行するvPortEnterCritical()は次以外になかった。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c#L408-L422

amenaruyaamenaruya

uxTaskGetStackHighWaterMark

FreeRTOSに於いて、各タスクのスタック領域の堆積現況を知る術がある。

https://qiita.com/azuki_bar/items/af3ababf0a986fe61097#uxtaskgetstackhighwatermark

勿論、Seeed_Arduino_FreeRTOSにも定義がある。

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/FreeRTOS/Source/include/task.h#L1512

https://github.com/Seeed-Studio/Seeed_Arduino_FreeRTOS/blob/3ce388eba7e79e67291f22fac406aed37052005a/src/tasks.c#L3902-L3927

これについて、次の記事が比較的分かりやすく実用例を示している。

https://asukiaaa.blogspot.com/2022/05/increase-stack-size-for-task-on-freertos.html

erpc::Threadの欠陥

uxTaskGetStackHighWaterMarkは、引数にTaskHandle_t型変数を定めている。通常の運用であれば、タスクを作るに当たって必ずTaskHandle_t型変数を使用するため、この定義に疑問はない。

example
#include <Seeed_Arduino_FreeRTOS.h>

/* 定義 */
TaskHandle_t taskA;

static void ThreadA(void* pvParameters) {
    /* スタック領域確認のために使用する */
    Serial.println(uxTaskGetStackHighWaterMark(taskA));
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);
    /* タスク作成のために使用する */
    xTaskCreate(ThreadA, "Task A", 256, NULL, tskIDLE_PRIORITY, &taskA);
    vTaskStartScheduler();
}
void loop() {}

しかしerpc::Threadの場合、TaskHandle_t型変数を定義することはない。

https://github.com/Seeed-Studio/Seeed_Arduino_rpcUnified/blob/master/src/erpc/erpc_threading.h#L209-L212

これらはprivate変数として定義されているため、クラス外部からは完全に隠蔽されている。また、uxTaskGetStackHighWaterMarkに相当する関数も定義されていない。

即ち、erpc::Threadをそのまま使用している以上、uxTaskGetStackHighWaterMarkなどを使う方法はない。せめてprotectedであれば、継承によって如何様にもなっただろうに。

amenaruyaamenaruya

解決

スタック領域を確認することができないため、確認するまでもない程度に増強したところ、呆気なく動いた。これまでの苦労は夢の跡と成り果てた。

large stack size
#include "rpcWiFi.h"

//#define STACK_SIZE 256
#define STACK_SIZE 4096

using namespace erpc;

static void ThreadA(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread A\tStarted");

    unsigned int flag = 10;

    //float flag2 = static_cast<float>(flag) / 3.3f;

    double flag3 = static_cast<double>(flag) / 3.3d;

    while (1) {
        Serial.println("Thread A\tHi");

        /*Serial.printf("Thread A\tflag: %u\n", flag);*/

        /*
        int printed;
        char string[24];
        printed = snprintf(
            string,
            24,
            "Thread A\tflag: %u\n",
            flag
        );
        if (printed >= 0)
            Serial.println(string);
        */

        Serial.print("Thread A\tflag: ");
        Serial.println(flag);

        /*
        Serial.print("Thread A\tfloat: ");
        Serial.println(flag2);
        */

        Serial.print("Thread A\tdouble: ");
        Serial.println(flag3);

        flag++;
        //flag2 = static_cast<float>(flag) / 3.3f;
        flag3 = static_cast<double>(flag) / 3.3d;

        delay(1000);
    }
}

static void ThreadB(void* pvParameters) {
    (void) pvParameters;
    Serial.println("Thread B\tStarted");

    while (1) {
        for (int i = 0; i < 10; i++) {
            Serial.println("Thread B\tHello?");
            delay(2000);
        }
        Serial.println("Thread B\tloop");
    }
}

void setup() {
    Serial.begin(115200);
    vNopDelayMS(1000);
    while(!Serial);
    /* error output */
    vSetErrorSerial(&Serial);

    Thread TaskA(
        &ThreadA,
        configMAX_PRIORITIES - 10,
        STACK_SIZE,
        "Task A"
    );

    Thread TaskB(
        &ThreadB,
        configMAX_PRIORITIES - 11,
        STACK_SIZE,
        "Task B"
    );

    Thread* tasksArray[2] = {&TaskA, &TaskB};

    for (Thread* t : tasksArray) {
        t -> start();
    }
}

void loop() {}

serial monitor
Thread A	Started
Thread A	Hi
Thread A	flag: 10
Thread A	double: 3.03
Thread B	Started
Thread B	Hello?
Thread A	Hi
Thread A	flag: 11
Thread A	double: 3.33
Thread B	Hello?
Thread A	Hi
Thread A	flag: 12
Thread A	double: 3.64
Thread A	Hi
Thread A	flag: 13
Thread A	double: 3.94
Thread B	Hello?
Thread A	Hi
Thread A	flag: 14
Thread A	double: 4.24
Thread A	Hi
Thread A	flag: 15
Thread A	double: 4.55
Thread B	Hello?
Thread A	Hi
Thread A	flag: 16
Thread A	double: 4.85
Thread A	Hi
Thread A	flag: 17
Thread A	double: 5.15
Thread B	Hello?
Thread A	Hi
Thread A	flag: 18
Thread A	double: 5.45
Thread A	Hi
Thread A	flag: 19
Thread A	double: 5.76
Thread B	Hello?
Thread A	Hi
Thread A	flag: 20
Thread A	double: 6.06
Thread A	Hi
Thread A	flag: 21
Thread A	double: 6.36