【Wio Terminal】Stack Overflow in FreeRTOS: stack pointer limit
Error Serial
これまで、FreeRTOSの停止するこそ夥多あれ、その実情を知る機会は殆ど無かった。唯一知る術であったのは、User LEDの点滅回数が示すエラーコードであった。此度、漸う第二の術を知ることができた。
汎用OSに於いても、標準出力の他にエラー出力というものが存在する。似たるもののありせば利便にも供せましと永らく不満に感じていたが、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() {}
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()
に従っている。
この発動箇所はここと見られる。
着目するのは玆。
/* 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: 呼び出し元関数名 :#行数
を表すものと思われる。この定義は複数存在する。
ここでassertBlink()
とは、一回の点滅を繰り返すものであり、今回の点滅回数と一致する。
コメントにある通り、configASSERT
に失敗したことを示している。
🤔🤔🤔
また、vPortEnterCritical()
も複数存在するが、420行目にconfigASSERT()
を実行するvPortEnterCritical()
は次以外になかった。
?
uxTaskGetStackHighWaterMark
FreeRTOSに於いて、各タスクのスタック領域の堆積現況を知る術がある。
勿論、Seeed_Arduino_FreeRTOSにも定義がある。
これについて、次の記事が比較的分かりやすく実用例を示している。
erpc::Threadの欠陥
uxTaskGetStackHighWaterMark
は、引数にTaskHandle_t
型変数を定めている。通常の運用であれば、タスクを作るに当たって必ずTaskHandle_t
型変数を使用するため、この定義に疑問はない。
#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
型変数を定義することはない。
これらはprivate
変数として定義されているため、クラス外部からは完全に隠蔽されている。また、uxTaskGetStackHighWaterMark
に相当する関数も定義されていない。
即ち、erpc::Thread
をそのまま使用している以上、uxTaskGetStackHighWaterMark
などを使う方法はない。せめてprotected
であれば、継承によって如何様にもなっただろうに。
解決
スタック領域を確認することができないため、確認するまでもない程度に増強したところ、呆気なく動いた。これまでの苦労は夢の跡と成り果てた。
#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() {}
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