11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FreeRTOSヒープメモリ管理まとめ

Last updated at Posted at 2021-02-13

ヒープメモリ

ヒープメモリとはプログラムの実行中に確保されるメモリをさします。Cのmalloc()関数で確保されるメモリと言えばC言語に馴染みのある人にはわかると思います。FreeRTOSはカーネルオブジェクト(タスク、キュー、セマフォ)をヒープメモリ上に配置します。このことは、プログラマがカーネルオブジェクトのメモリ配置を考慮する負担をなくします。ヒープメモリはRTOSに不可欠な構成要素です。

通常のWindowsアプリではmalloc()free()関数を用いてヒープメモリの管理が行われますが、組み込み環境ではそのようにはいきません。これは以下の理由によるためです。

  1. ほとんどの組み込み環境ではmalloc()free()がサポートされていない
  2. 大抵のmalloc()free()関数の実装は巨大でメモリが限られている組み込み環境に適さない
  3. 大抵スレッドセーフでない
  4. 処理時間が確保するメモリサイズや呼び出しタイミングに左右される
  5. フラグメンテーションが発生する
  6. リンカー設定を修正する必要がある
  7. ヒープに確保されたメモリのサイズが大きくなるにつれて困難なバグの発生原因となる

FreeRTOSではmalloc()free()の代わりにpvPortMalloc()関数とvPortFree()関数を使います。

void heap_sample()
{
    char* message;

    message = (char*) pvPortMalloc(4); // 確保するサイズを引数で指定。4バイト確保
    message[0] = 'H';
    message[1] = 'i';
    message[2] = '!';
    message[3] = 0;

    pvPortFree(message); // 使い終わったら解放する。
}

pvPortMalloc()関数とvPortFree()関数の実装は自分で定義することができます。FreeRTOSではFreeRTOS/Source/portable/MemMangディレクトリに以下5種類の実装を提供しています。

  1. Heap_1
  2. Heap_2
  3. Heap_3
  4. Heap_4
  5. Heap_5

次節からはHeap_1からHeap_5の詳細を順に説明します。

Heap_1

FreeRTOS/Source/portable/MemMang/heap_1.cに実装されています。
Heap_1は最も単純な実装です。コンパイル時に配列として割り当てられたメモリ領域をpvPortMalloc()関数が呼び出されるたびに切り出して、ポインタとして返します。Heap_1はメモリを割り当てるだけで削除することはありません。したがって、vPortFree()は実装されていません。
コンパイル時に割り当てるメモリ領域のサイズはconfigTOTAL_HEAP_SIZE変数の値を修正することで変更することができます。
Heap_1は動的メモリを割り当てるだけで解放することはできません。注意深くプログラミングしないとすぐに枯渇してしまうという問題がありますが、フラグメンテーションなど動的メモリの割当てに伴う問題が発生しないというメリットがあります。

Heap_2

Heap_2はHeap_1と同様にconfigTOTAL_HEAP_SIZE変数でサイズが指定された配列を切り分けて動的メモリとして使用します。Heap_1との違いは

  • メモリの割当にベストフィットアルゴリズムを使用すること
  • 割り当てられたメモリをvPortFree()で解放できる

Heap_2は後方互換のために用意されているので使用は推奨されていません。代わりにHeap_4を使用してください。

ベストフィットアルゴリズムはpvPortMalloc()関数で要求されたメモリサイズに最も近いサイズの空きメモリを返すことを保証します。このことは、pvPortMalloc()関数とvPortFree()関数によって確保される・開放されるメモリのサイズが毎回同じときに効率よくメモリの割当てが行うことができます。

Heap_3

Heap_3は標準ライブラリのmalloc()関数とfree()関数が使われます。configTOTAL_HEAP_SIZE変数は無視されます。スレッドの同期にためにスケジューラによるタスクの切り替えはpvPortMalloc()vPortFree()関数の呼び出しの間は停止されます。

Heap_4

Heap_1とHeap_2と同様に配列を小分けにしてメモリの割当を行います。メモリの割当てにはファーストフィットアルゴリズムが使われます。ファーストフィットアルゴリズムはHeap_2で用いられているベストフィットアルゴリズムよりもフラグメンテーションに強いアルゴリズムです。
Heap_4はサイズのみならず割当てに使われる配列のアドレスを変更することができます。
FreeRTOSConfig.hconfigAPPLICATION_ALLOCATED_HEAP変数の値を1にすると、割当に使われる配列の実装はuint8_t型の配列ucHeapが使われます。ucHeapはアプリケーション側で定義する必要があります。また、configTOTAL_HEAP_SIZEのサイズである必要があります。
特定のアドレスに配列を配置する方法はコンパイラーによって変わります。GCCの場合は__attribute__を使って配置するアドレス(セクション)を以下のように指定できます。

uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] __attribute__ ( ( section( ".my_heap" ) ) );

IARのコンパイラーの場合は以下のように指定できます。

uint8_t ucHeap[ configTOTAL_HEAP_SIZE ] @ 0x20000000;

configAPPLICATION_ALLOCATED_HEAP変数の値が0の時はリンカーによって自動的に割り当てに使われる配列のアドレスが決定されます。

Heap_5

Heap_5はheap_4と同じアルゴリズムをメモリの割当て用います。Heap_4との違いは複数の分割されたメモリ空間からの割当ができる点です。その反面、Heap_5は初期化処理が必要です。すなわち、pvPortMalloc()関数を呼び出す前にvPortDefineHeapRegions()関数を用いて割当に使われるメモリのアドレスとサイズを設定する必要があります。以下にvPortDefineHeapRegions()を使って割当に使われるメモリ領域を設定する初期化処理を示します。

#define HEAP_A_ADDR    ((uint8_t*)0x01000000) // 領域Aの開始アドレス
#define HEAP_A_SIZE    0x00001000 // 領域Aのサイズ
#define HEAP_B_ADDR    ((uint8_t*)0x02000000) // 領域Bの開始アドレス
#define HEAP_B_SIZE    0x00001000 // 領域Bのサイズ

// 警告:各領域は重複しないようにしてください。

const HeapRegion_t xHeapRegions[] =
{
{ HEAP_A_ADDR, HEAP_A_SIZE },
{ HEAP_B_ADDR, HEAP_B_SIZE },
{ NULL,        0           } /* 配列の終わりの目印 */
};

void init_heap( void )
{
    // 領域Aと領域BをHeap_5のメモリ割り当てに使うように指定
    vPortDefineHeapRegions(xHeapRegions);
}

ヒープユーティリティ関数

pvPortMalloc()関数とvPortFree()関数以外にもヒープの操作に使われる関数があります。ここでは、pvPortMalloc()関数とvPortFree()関数以外のヒープユーティリティ関数について説明します。

xPortGetFreeHeapSize()

現在、利用可能なヒープのサイズを取得します。主にヒープサイズの最適化のために使われます。

xPortGetMinimumEverFreeHeapSize()

今まで使用されることのなかったヒープのサイズを返します。これも、xPortGetFreeHeapSize()と同様にヒープサイズの最適化のために使われます。

vApplicationMallocFailedHook()

こちらはヒープが枯渇したときに呼ばれるフック関数です。アプリケーション側で実装します。この関数にメモリの割当に失敗したときの例外処理を実装します。デフォルトではpvPortMalloc()関数がメモリの割当に失敗したときは、NULLを返すだけです。NULLを返す代わりにフック関数が呼ばれるようにするには、FreeRTOSConfig.hconfigUSE_MALLOC_FAILED_HOOKを1に設定します。

参考文献

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?