FreeRTOSのメモリ管理方法(超詳しく)

メモリ管理

        RTOS カーネルは、タスク、キュー、ミューテックス、ソフトウェア タイマー、セマフォ、またはイベント グループが作成されるたびに RAM を必要とすることがわかっています。RAM は RTOS API オブジェクトから取得できます。作成関数内の RTOS ヒープは、自動的に動的に割り当てられるか、アプリケーション作成者によって提供されます

        RTOS オブジェクトが動的に作成される場合、標準 C ライブラリの malloc() 関数と free() 関数をこの目的に使用できる場合がありますが、組み込みシステム 常に利用できるわけではなく、貴重なコード スペースを占有し、スレッドセーフではなく、決定的ではありません (関数の実行にかかる時間は呼び出しごとに異なります) 。多くの場合、必要なのは代替メモリ割り当ての実装ではありません。

        ある組み込み/リアルタイム システムの RAM およびタイミング要件は、別のシステムとは大きく異なる場合があるため、単一の RAM 割り当てアルゴリズムはアプリケーションのサブセットに対してのみ機能します。

        この問題を回避するために、FreeRTOS はメモリ割り当て API をポータブル レイヤに保持します。移植性レイヤーにより、コア RTOS 機能を実装するソース ファイルに加えて、開発中のリアルタイム システムに適したアプリケーション固有の実装を提供できます。 RTOS カーネルは RAM を必要とする場合、malloc() を呼び出す代わりに pvPortMalloc() を呼び出します。 RAM を解放するとき、RTOS カーネルは free() ではなく vPortFree() を呼び出します。

        FreeRTOS は、複雑さと機能が異なるいくつかのヒープ管理オプションを提供します。独自のヒープ実装を提供したり、2 つのヒープ実装を同時に使用したりすることもできます。両方のヒープを同時に使用すると、タスク スタックとその他の RTOS オブジェクトを内部 RAM に配置し、アプリケーション データを低速の外部 RAM に配置できます。

5つの管理方法

        ソース コードには、5 つのメモリ管理方法に対応する 5 つのファイルがデフォルトで提供されます。 提供される各実装は、メインの RTOS ソースにある個別のソース ファイル (それぞれ heap_1.c、heap_2.c、heap_3.c、heap_4.c、および heap_5.c) に含まれています。コードのダウンロード コンテンツは、Source/Portable/MemMang ディレクトリにあります。 必要に応じて他の実装を追加できます。これらのソース ファイルは一度に 1 つだけプロジェクトに含める必要があります (RTOS を使用するアプリケーションが独自のヒープ実装を使用することを選択した場合でも、これらのポータブル層関数によって定義されたヒープは RTOS カーネルによって使用されます)。

heap_1 - 最も単純で、メモリの解放を許可しません。

heap_2 - メモリの解放を許可しますが、隣接する空きブロックはマージされません。

heap_3 - スレッドの安全性を確保するための、標準の malloc() と free() の単純なラッパー。

heap_4 - 断片化を避けるために、隣接する空きブロックをマージします。絶対アドレス配置オプションが含まれます。

heap_5 - heap_4 と同様、複数の非連続メモリ領域にまたがることができるヒープ。

知らせ:

        FreeRTOS が静的割り当てサポートを追加したため、Heap_1 はあまり役に立ちません。

        heap_2 は、新しい heap_4 実装が優先されるため、レガシーとみなされます。

heap_1.c

        FreeRTOS が静的割り当てサポートを追加したため、Heap_1 はあまり役に立ちません。 heap_1 は最も単純な実装です。メモリが一度割り当てられると、そのメモリを再度解放することはできません。それにもかかわらず、heap_1.c は多くの組み込みアプリケーションに適しています。これは、多くの小規模で深く埋め込まれたアプリケーションは、システムの起動時に必要なすべてのタスク、キュー、セマフォなどを作成し、プログラムの存続期間中 (アプリケーションが閉じられるか再起動されるまで) これらすべてのオブジェクトを使用するためです。コンテンツは削除されません。

        この実装は、RAM が必要な場合に、単一の配列をより小さなチャンクに分割するだけです。配列の合計サイズ (ヒープの合計サイズ) は、configTOTAL_HEAP_SIZE (FreeRTOSConfig.h で定義) によって設定されます。 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 構成定数は、ヒープをメモリ内の特定のアドレスに配置できるようにするために提供されています。

        xPortGetFreeHeapSize() API 関数は、未割り当てのヒープ領域の合計量を返し、configTOTAL_HEAP_SIZE 設定の最適化を可能にします。

heap_1 の実装

        これは、アプリケーションがタスク、キュー、セマフォ、ミューテックスなどを削除しない場合に使用できます。 (これは実際には、FreeRTOS を使用するほとんどのアプリケーションをカバーしています)。

        常に決定的 (実行には常に同じ時間がかかります) で、メモリの断片化を引き起こしません。これは非常にシンプルで、静的に割り当てられた配列からメモリを割り当てます。つまり、真の動的なメモリ割り当てができないアプリケーションに一般的に適しています。

heap_1 を使用する場合、メモリ割り当てプロセスは次の図に示すようになります。

A: タスクを作成する前は配列全体が空いています。

B: 最初のタスクが作成された後、青い領域が割り当てられました

C: 3 つのタスクを作成した後の配列の使用状況

ヒープ_2.c

        Heap_2 はレガシーとみなされ、heap_4 が優先されます。 heap_2 はベストフィット アルゴリズムを使用し、以前に割り当てられたブロックを解放できるシナリオ 1 とは異なり、隣接する空きブロックを 1 つの大きなブロックに結合しません。空きブロックをマージしない実装については、heap_4.c を参照してください。

        使用可能なヒープ領域の合計量は、configTOTA L_HEAP_SIZE (FreeRTOSConfig.h で定義) によって設定されます。 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 構成定数は、ヒープをメモリ内の特定のアドレスに配置できるようにするために提供されています。 xPortGetFreeHeapSize() API 関数は、未割り当てのヒープ領域の合計量を返します (configTOTAL_HEAP_SIZE 設定の最適化が可能) が、未割り当てのメモリがどのように小さなチャンクに断片化されるかについての情報は提供しません。

        pvPortCalloc() 関数のシグネチャは、標準ライブラリの calloc 関数と同じです。オブジェクトの配列にメモリを割り当て、割り当てられたストレージ内のすべてのバイトをゼロに初期化します。割り当てが成功すると、割り当てられたメモリ ブロック内の最下位バイトへのポインタが返されます。割り当てが失敗した場合は、null ポインタが返されます。

heap_2 の実装

        アプリケーションがタスク、キュー、セマフォ、ミューテックスなどを重複排除しても、それらは引き続き使用できますが、メモリの断片化に関する次の情報に注意してください。割り当ておよび解放されるメモリのサイズがランダムな場合は使用できません。

例えば:

        アプリケーションがタスクを動的に作成および削除し、作成されるタスクに割り当てられるスタック サイズが常に同じである場合は、ほとんどの場合 heap2.c を使用できます。ただし、作成中のタスクに割り当てられるスタックのサイズが常に同じであるとは限らない場合、利用可能な空きメモリが多数の小さな部分に断片化され、最終的に割り当てが失敗する可能性があります。この場合、heap_4.c の方が良い選択です。

        アプリケーションがタスクを動的に作成および削除し、キューのストレージ領域がそれぞれのケースで同じである場合 (キューのストレージ領域は、キュー項目のサイズとキューの長さの積です)、ほとんどの場合、heap_2.c を使用できます。ただし、キューのストレージ領域がそれぞれのケースで同じではない場合、使用可能な空きメモリが多数の小さな部分に断片化され、最終的に割り当てが失敗する可能性があります。この場合、heap_4.c の方が良い選択です。

        アプリケーションは、他の FreeRTOS API 関数を介して間接的にではなく、pvPortMalloc() および vPortFree() を直接呼び出します。

        アプリケーションのキュー、タスク、セマフォ、ミューテックスなどが予測できない順序になっている場合、メモリの断片化が発生する可能性があります。これはほとんどすべてのアプリケーションで可能であるわけではありませんが、留意する必要があります。非決定的ですが、ほとんどの標準 C ライブラリの malloc 実装よりも効率的です。

        heap_2.c は、オブジェクトを動的に作成する必要がある多くの小規模なリアルタイム システムに適しています。空きメモリ ブロックを単一の大きなメモリ ブロックに結合する同様の実装については、heap_4 を参照してください。

heap_2 を使用する場合、メモリ割り当てプロセスは次の図のようになります。

A: 3 つのタスクが作成されました

B: タスクが削除され、空きメモリにはトップレベル、削除されたタスクの TCB 空間、および削除されたタスクのスタック空間の 3 つの部分があります。

C: 新しいタスクが作成されますが、TCB とスタック サイズは以前に削除されたタスクと同じであるため、元のメモリが割り当てられるだけです。

ヒープ_3.c

        これは、標準 C ライブラリの malloc() および free() 関数用に実装された単純なラッパーであり、ほとんどの場合、選択したコンパイラとともに提供されます。このラッパーは単に malloc() 関数と free() 関数をスレッドセーフにするだけです。

heap_3 の実装

        リンカーはヒープを設定する必要があり、コンパイラー ライブラリは malloc() および free() 実装を提供する必要があります。決定的ではないため、RTOS カーネルのコード サイズが大幅に増加する可能性があります。

        heap_3 を使用する場合、FreeRTOSConfig.h の configTOTAL_HEAP_SIZE 設定は効果がないことに注意してください。

ヒープ_4.c

         このスキームは最初の適応アルゴリズムを使用し、スキーム 2 とは異なり、隣接する空きメモリ ブロックを単一の大きなメモリ ブロックに形成します (マージ アルゴリズムが含まれます)。

        使用可能なヒープ領域の合計量は、configTOTAL_HEAP_SIZE (FreeRTOSConfig.h で定義) によって設定されます。 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 構成定数は、ヒープをメモリ内の特定のアドレスに配置できるようにするために提供されています。

        xPortGetFreeHeapSize() API 関数は、呼び出されると未割り当てのヒープ領域の合計量を返し、xPortGetMinimumEverFreeHeapSize() API 関数は、FreeRTOS アプリケーションの起動時にシステム上にすでに存在する空きヒープ領域の最小量を返します。どちらの関数も、未割り当てメモリがどのように小さなチャンクに断片化されるかについての情報を提供しません。

        vPortGetHeapStats() API 関数は追加情報を提供します。以下に示すように、heap_t 構造体のメンバーを埋めます。

/* Prototype of the vPortGetHeapStats() function. */
void vPortGetHeapStats( HeapStats_t *xHeapStats );

/* Definition of the Heap_stats_t structure. */

typedef struct xHeapStats
{
       size_t xAvailableHeapSpaceInBytes;      /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */
       size_t xSizeOfLargestFreeBlockInBytes;     /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xNumberOfFreeBlocks;            /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
       size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
       size_t xNumberOfSuccessfulAllocations;   /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
       size_t xNumberOfSuccessfulFrees;     /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;

        pvPortCalloc() 関数のシグネチャは、標準ライブラリの calloc 関数と同じです。オブジェクトの配列にメモリを割り当て、割り当てられたストレージ内のすべてのバイトをゼロに初期化します。割り当てが成功すると、割り当てられたメモリ ブロック内の最下位バイトへのポインタが返されます。割り当てが失敗した場合は、null ポインタが返されます。

heap_4 が使用する

        アプリケーションがタスク、キュー、セマフォ、ミューテックスなどを繰り返し削除した場合でも、それらは引き続き使用できます。

        Heap_2の実装よりも複数の小さなチャンクにヒープ空間を重度の断片化する可能性が低い(メモリが割り当てられ、解放されている場合でも、ランダムサイズであっても)。決定的ではありませんが、ほとんどの標準 C ライブラリの malloc 実装よりも効率的です。

        heap_4.c は、(API 関数 pvPortMalloc() および vPortFree() を呼び出して間接的にではなく) アプリケーション コード内でポータブル レイヤーのメモリ割り当てスキームを直接使用したいアプリケーションに特に役立ちます。

        Heap_4 の実行時間は不明ですが、その効率は malloc よりも高く、標準ライブラリは不要です。

ヒープ_5.c

         このスキームは、heap_4 と同じ最初の適合およびメモリ マージ アルゴリズムを使用し、ヒープが複数の隣接しない (連続していない) メモリ領域にまたがることを可能にします。 Heap_5 は、vPortDefineHeapRegions() を呼び出すことによって初期化され、vPortDefineHeapRegions() が実行されるまで使用しないでください。 RTOS オブジェクト (タスク、キュー、セマフォなど) を作成すると、暗黙的に pvPortMalloc() が呼び出されるため、heap_5 を使用する場合は、そのようなオブジェクトを作成する前に vPortDefineHeapRegions() を呼び出すことが重要です。

        vPortDefineHeapRegions() は、HeapRegion_t 構造体の配列である単一のパラメーターを取ります。 HeapRegion_t は、portable.h で次のように定義されています。

typedef struct HeapRegion
{
    /* Start address of a block of memory that will be part of the heap.*/
    uint8_t *pucStartAddress;

    /* Size of the block of memory. */
    size_t xSizeInBytes;
} HeapRegion_t;

The HeapRegion_t type definition

        配列は、空のサイズがゼロの領域定義で終了します。配列内で定義されたメモリ領域は、下位アドレスから上位アドレスのアドレス順に表示する必要があります。次のソース コード スニペットは例を示しています。さらに、MSVC Win32 エミュレーターのデモでも heap_5 が使用されているため、リファレンスとして使用できます。

/* Allocate two blocks of RAM for use by the heap.  The first is a block of
0x10000 bytes starting from address 0x80000000, and the second a block of
0xa0000 bytes starting from address 0x90000000.  The block starting at
0x80000000 has the lower start address so appears in the array fist. */

const HeapRegion_t xHeapRegions[] =
{
    { ( uint8_t * ) 0x80000000UL, 0x10000 },
    { ( uint8_t * ) 0x90000000UL, 0xa0000 },
    { NULL, 0 } /* Terminates the array. */
};
/* Pass the array into vPortDefineHeapRegions(). */
vPortDefineHeapRegions( xHeapRegions );

Initialising heap_5 after defining the memory blocks to be used by the heap

        xPortGetFreeHeapSize() API 関数は、呼び出されると未割り当てのヒープ領域の合計量を返し、xPortGetMinimumEverFreeHeapSize() API 関数は、FreeRTOS アプリケーションの起動時にシステム上にすでに存在する空きヒープ領域の最小量を返します。どちらの関数も、未割り当てメモリがどのように小さなチャンクに断片化されるかについての情報を提供しません。

        pvPortCalloc() 関数のシグネチャは、標準ライブラリの calloc 関数と同じです。オブジェクトの配列にメモリを割り当て、割り当てられたストレージ内のすべてのバイトをゼロに初期化します。割り当てが成功すると、割り当てられたメモリ ブロック内の最下位バイトへのポインタが返されます。割り当てが失敗した場合は、null ポインタが返されます。

        vPortGetHeapStats() API 関数は、ヒープ ステータスに関する追加情報を提供します。

ヒープ関連関数

pvポートMalloc/vPortFree

関数プロトタイプ

void * pvPortMalloc( size_t xWantedSize ); // 分配内存,如果分配内存不成功,则返回值为NULL。

void vPortFree( void * pv );  // 释放内存

        機能: メモリの割り当てとメモリの解放。メモリの割り当てが失敗した場合、戻り値は NULL になります。

xPortGetFreeHeapSize

関数プロトタイプ

size_t xPortGetFreeHeapSize( void );

        現在利用可能な空きメモリはどのくらいですか? この機能を使用すると、メモリ使用量を最適化できます。たとえば、すべてのカーネル オブジェクトが割り当てられた後、この関数を実行すると 2000 が返され、その後 configTOTAL_HEAP_SIZE を 2000 減らすことができます。

        注: heap_3 では使用できません。

xPortGetMinimumEverFreeHeapSize

関数プロトタイプ

size_t xPortGetMinimumEverFreeHeapSize( void );

        - - '' '' '' ''フリーメモリ容量は、実行中のプログラムに保存されます。

        注: この関数は heap_4 と heap_5 のみがサポートしています。

malloc 失敗したフック関数

void * pvPortMalloc( size_t xWantedSize )
{
    ......
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
        {
            if( pvReturn == NULL )
            {
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
        }
    #endif
    return pvReturn;       
}

        pvPortMalloc 関数内で、このフック関数を使用する場合: FreeRTOSConfig.h で、configUSE_MALLOC_FAILED_HOOK を 1 として定義します。 vApplicationMallocFailedHook 関数を提供する

        この関数は、pvPortMalloc が失敗した場合にのみ呼び出されます。

一般的に使用される管理方法

動的メモリ割り当て

        FreeRTOS は、pvPortMalloc や vPortFree などの動的メモリ割り当てのための組み込み関数を提供します。これらの関数により、タスクは実行時にメモリを要求および解放できます。このアプローチは、柔軟なメモリ管理を必要とするアプリケーションに役立ちますが、メモリ リークや断片化を避けるために注意する必要があります。

静的メモリ割り当て

        FreeRTOS を使用すると、ユーザーはコンパイル時にタスクやカーネル オブジェクト (キュー、セマフォなど) に静的メモリを割り当てることができます。 FreeRTOS 構成ファイルで適切なマクロを定義することにより、タスクのスタックとカーネル オブジェクトのメモリを静的に割り当てることができます。

メモリプール

        メモリ プールは、システムの初期化中に作成されるメモリ領域で、固定サイズのメモリ ブロックを格納するために使用されます。タスクはメモリ プールからメモリ ブロックを要求し、使用後にメモリ プールにメモリ ブロックを返すことができます。これはメモリの断片化を軽減するのに役立ちます。

スタックの自動拡張

        FreeRTOS を使用すると、タスクのスタックが実行時に自動的に拡張され、タスクの実行中の動的なニーズに適応できます。このアプローチでは、タスクの実行時にスタックの最大の深さを事前に知る必要がなくなりますが、スタックのオーバーフローを防ぐための注意も必要になります。

静的 API と動的 API の混合使用

        FreeRTOS では、静的 API と動的 API を選択して使用でき、アプリケーションの要件に応じて適切な方法を選択できます。これにより、一部のタスクには静的割り当てを使用し、他のタスクには動的割り当てを使用して、それぞれのアプローチを最大限に活用することができます。

        FreeRTOS は柔軟なメモリ管理メカニズムを提供しており、特定のアプリケーション シナリオに応じて適切な方法を選択できます。動的メモリ割り当てを使用する場合は、メモリ リークと断片化の問題に特別な注意を払う必要がありますが、静的メモリ割り当てとメモリ プールを使用すると、これらの問題をある程度軽減できます。

おすすめ

転載: blog.csdn.net/m0_56694518/article/details/134901019