FreeRTOS理解その10（スタックサイズ）

久しぶりのFreeRTOS関連。

## スタック使用量を調べる
FreeRTOSにて、[uxTaskGetStackHighWaterMark()](https://www.freertos.org/uxTaskGetStackHighWaterMark.html)を用いて、タスクの残スタックサイズを確認する。

## 環境
Nucleo STM32F303REボードをArduino IDE上で利用。

## テストコード
タスクを一つ生成。ボタンが押されたら、そのタスクの残スタックサイズを表示する。ただし、タスクの残スタックサイズ表示にボタン押下の必然性はなく、深い理由もなく、そうしたのみ。

ボタン押下ハンドリングはattachInterrupt()を用い、対応するハンドラ内で生成したタスクの残スタックサイズを表示する（ハンドラ内で処理時間がかかる処理を行うのは好ましくないが）。setup()内でハンドラ設定、タスク生成を行う。

以降、①から⑬の表記があるが、コード上、同時にこれらすべてを実行していない。これらの様々な組み合わせで、残スタックサイズの変化を表示することを意図しているためである。

```c++
#include <STM32FreeRTOS.h>
TaskHandle_t xTask;

void InterruptHandler() {
  char buf[4];
  sprintf(buf, "%d", uxTaskGetStackHighWaterMark(xTask));
  Serial.println(buf);  
}

void setup() {
  Serial.begin(9600);
  pinMode(USER_BTN, INPUT);
  attachInterrupt(digitalPinToInterrupt(USER_BTN), InterruptHandler, LOW);
  // xTaskCreate(Task, "Task", 128, NULL, 0, &xTask);    // ①
  uint32_t val = 123;                                    // ②
  xTaskCreate(Task, "Task", 32, (void*)&val, 0, &xTask); // ③
  vTaskStartScheduler();
}  
```

loop()もタスクの一種らしいので、loop()でも残スタックサイズを表示（引数にNULLを指定すると自タスクが対象となる）。

```c++
void loop() {
  char buf[32];　　　　　　                                         // ④
  static uint32_t i = 0;                                         // ⑤
  sprintf(buf, "%d %d", uxTaskGetStackHighWaterMark(NULL), i++); // ⑥
  // sprintf(buf, "%d", uxTaskGetStackHighWaterMark(NULL));      // ⑦
  Serial.println(buf);	
  delay(2000);
}
```

生成されるタスクのコード。種々の状況時の残スタックサイズを測るためのコードであり、内容に意味はない。

```c++
void Task(void *pvParameters) {
  static uint32_t i = 0;                     // ⑧
  //uint32_t j = *((uint32_t*)pvParameters); // ⑨
  for (;;) {
    //Serial.println("Task");                // ⑩
    i++;                                     // ⑪
    vTaskDelay(2000);                        // ⑫
    //vTaskDelay(2000+j);                    // ⑬
  }
}
```

## 結果
### loop()の残スタックサイズ
まずはbufサイズを変化させて調べる。Serial.println()による表示結果として、初回コール時の残スタックサイズはいずれも117 byteであり、2回目以降の残スタックサイズは下記表のとおりであった。

|bufサイズ（上記④）|32|16|8|4|
|---|---|---|---|---|
|残サイズ（上記⑥または⑦）|14|18|20|20|

- bufサイズに関わらず1回目の数値の差が同じ理由、bufサイズが8バイトと4バイトの場合に2回目以降で同じ20バイトとなる理由は不明。（未調査）
- ⑤と⑥とをコメントアウトし（変数'i'を用いず）、⑦のみコード化しても結果は同じ。

### 生成したタスクの残スタックサイズ
#### スタックサイズ128バイト指定
xTaskCreate()にて第3引数を"128"にしたとき（①選択、③未選択）に、InterruptHandler()（ボタン押下時）で表示されるタスクの残スタックサイズは下記表のとおりである。 

|ケース|⑫のみ|⑩"Task"と⑫|⑩"TaskTask"と⑫|⑧と⑫|⑧と⑪と⑫|
|---|---|---|---|---|---|
|残サイズ|113|102|102|113|105|

⑫のみとは、⑧から⑬のうち⑫のみ実行された場合のサイズである。その他も同様。また、⑩"Task"とはSerial.println()の引数が"Task"の場合、⑩"TaskTask"とはその引数が"TaskTask"を表す。

- Staticで領域を確保しただけではサイズは変わらない（⑫の結果と「⑧と⑫」の結果とが同じ）。変数の演算があってサイズは変化する（⑧と⑪と⑫の結果）。
- ”Task"と"TaskTask"とで差が出ないのは、ボタン押下時にはこの処理を行っていないことによるものか？

#### タスク作成時にスタックサイズを変化させる
⑧と⑪と⑫実行時における残スタックサイズ。

|タスク生成時指定スタックサイズ|256|128|64|32|
|---|---|---|---|---|
|残サイズ|233|105|41|9|

- 差はすべて23バイト。予想どおり。

#### タスク引数の利用（タスク生成時の指定スタックサイズ128バイト）

|ケース|⑨と⑫|⑨と⑬|⑫のみ|
|---|---|---|---|
|残サイズ|113|105|113|

- 引数が使用されないと残スタックサイズは変化しない。

#### タスク生成時の指定スタックサイズ32バイト
⑧と⑪と⑫のSerial.println()の引数の違いによる残スタックサイズ。

|引数|Task|TaskTask|TaskTaskTaskTask|Task..Task(16個)|Task..Task(32個)
|---|---|---|---|---|---|
|残サイズ|6|6|6|3|おかしくなる|

- 最初の3ケースで差が出ないのは、やはり、ボタン押下時にはこの処理を行っていないことによるものか？
- 最後のケースではスタックオーバーフローを起こしている可能性が高い。

## 最後に
まずは、残スタックサイズを知ることがわかる。他の視点（アセンプラレベル？）で考察すればよいのだろうが、そこまでパワーがない。