ARM Cortex-M4マイクロコントローラのヒープおよびスタックサイズを定義していますか?


11

私は、小規模な組み込みシステムプロジェクトのオンとオフに取り組んでいます。これらのプロジェクトの一部は、ARM Cortex-M4ベースプロセッサを使用しました。プロジェクトフォルダーにstartup.sファイルがあります。そのファイル内で、次の2つのコマンドラインに注目しました。

;******************************************************************************
;
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Stack   EQU     0x00000400

;******************************************************************************
;
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
;******************************************************************************
Heap    EQU     0x00000000

マイクロコントローラのヒープスタックのサイズをどのように定義しますか?データシートに正しい値に到達するためのガイドとなる特定の情報はありますか?もしそうなら、データシートで何を探すべきですか?


参照:

回答:


12

スタックとヒープはソフトウェアの概念であり、ハードウェアの概念ではありません。ハードウェアが提供するのはメモリです。1つの「スタック」と1つの「ヒープ」と呼ばれるメモリのゾーンを定義することは、プログラムの選択です。

ハードウェアはスタックに役立ちます。ほとんどのアーキテクチャには、スタックポインターと呼ばれる専用のレジスタがあります。その用途は、プログラムが関数呼び出しを行うと、関数パラメーターと戻りアドレスがスタックにプッシュされ、関数が終了して呼び出し元に戻るときにポップされることです。スタックにプッシュするとは、スタックポインターによって指定されたアドレスに書き込み、それに応じてスタックポインターをデクリメントします(または、スタックが成長する方向に応じてインクリメントします)。ポッピングとは、スタックポインターをインクリメント(またはデクリメント)することです。戻りアドレスは、スタックポインタによって指定されたアドレスから読み取られます。

一部のアーキテクチャ(ARMではありません)には、ジャンプとスタックポインターで指定されたアドレスへの書き込みを組み合わせたサブルーチン呼び出し命令と、スタックポインターで指定されたアドレスからの読み取りとこのアドレスへのジャンプを組み合わせたサブルーチンリターン命令があります。ARMでは、アドレスの保存と復元はLRレジスタで行われ、呼び出し命令と戻り命令はスタックポインターを使用しません。ただし、スタックポインタによって指定されたアドレスへの複数のレジスタの書き込みまたは読み取りを容易にし、関数の引数をプッシュおよびポップするための命令があります。

ヒープとスタックのサイズを選択するために、ハードウェアからの関連情報は、総メモリ量だけです。次に、メモリに保存するものに応じて選択します(コード、静的データ、および他のプログラムを許可します)。

通常、プログラムはこれらの定数を使用して、スタックの最上部のアドレス、スタックオーバーフローをチェックする値、ヒープアロケーターの境界など、コードの残りで使用されるメモリ内のデータを初期化します。など

ご覧のコードでは、Stack_Size定数はコード領域にメモリブロックを予約するために使用されます(SPACEARMアセンブリのディレクティブを使用)。このブロックの上位アドレスにはlabelが付与__initial_spされ、ベクターテーブルに保存され(プロセッサはソフトウェアリセット後にこのエントリを使用してSPを設定します)、他のソースファイルで使用するためにエクスポートされます。Heap_Size定数は、同様に、その境界にメモリとラベルのブロックを予約するために使用される(__heap_base__heap_limit)他のソースファイルで使用するためにエクスポートされます。

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

…
__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler

…

                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit

あなたはそれらの値0x00200と0x000400がどのように決定されるか知っています
マヘンドラGunawardenaに

@MahendraGunawardenaあなたのプログラムが必要とするものに基づいて、それらを決定するのはあなた次第です。Niallの答えはいくつかのヒントを提供します。
ジル「SO-悪であるのをやめる」

7

スタックとヒープのサイズは、マイクロコントローラーのデータシートのどこでもなく、アプリケーションによって定義されます。

スタック

スタックは、関数内のローカル変数の値、ローカル変数に使用されるCPUレジスタの以前の値(関数の終了時に復元できるように)、それらの関数を離れるときに戻るプログラムアドレス、およびスタック自体の管理のためのオーバーヘッド。

組み込みシステムを開発するときは、予想される最大呼び出し深度を推定し、その階層内の関数内のすべてのローカル変数のサイズを合計してから、上記のオーバーヘッドを考慮してパディングを追加してから、プログラムの実行中に発生する可能性のある割り込み。

別の推定方法(RAMが制約されていない場合)は、必要以上に多くのスタックスペースを割り当て、スタックをセンチネル値で満たし、実行中に実際に使用する量を監視することです。これを自動的に行うC言語ランタイムのデバッグバージョンを見てきました。その後、開発が終了したら、必要に応じてスタックサイズを縮小できます。

ヒープ

必要なヒープのサイズを計算するのは難しい場合があります。ヒープは、あなたが使用している場合、動的に割り当てられた変数に使用されるmalloc()と、free()C言語プログラムで、またはnewおよびdeleteそれらの変数が住んでいるのですC ++、に。

ただし、特にC ++では、いくつかの隠された動的メモリ割り当てが行われる可能性があります。たとえば、オブジェクトが静的に割り当てられている場合、言語では、プログラムの終了時にデストラクタを呼び出す必要があります。デストラクタのアドレスが動的に割り当てられたリンクリストに保存されるランタイムを少なくとも1つ知っています。

したがって、必要なヒープのサイズを見積もるには、コールツリーを通る各パスのすべての動的メモリ割り当てを調べ、最大値を計算して、パディングを追加します。言語ランタイムは、合計ヒープ使用量、断片化などを監視するために使用できる診断を提供する場合があります。


応答をありがとう、私は等々 0x00400など、特定の番号を決定する方法に好き
マヘンドラGunawardena

5

他の答えに加えて、スタックとヒープスペースの間にRAMを作成するときは、静的な非定数データ(ファイルグローバル、関数静的、プログラム全体など)のスペースも考慮する必要があることを付け加えます。 Cの観点からのグローバル、およびおそらくC ++のその他)。

スタック/ヒープ割り当ての仕組み

スタートアップアセンブリファイルは、領域を定義する1つの方法であることに注意してください。ツールチェーン(ビルド環境とランタイム環境の両方)は、スタック空間の開始(ベクターテーブルに初期スタックポインターを格納するために使用)とヒープ空間の開始と終了(ダイナミックによって使用される)メモリアロケータ、通常はlibcによって提供されます)

OPの例では、1kiBのスタックサイズと0Bのヒープサイズの2つのシンボルのみが定義されています。これらの値は他の場所で使用され、実際にスタックとヒープスペースを生成します

@Gillesの例では、サイズが定義れ、アセンブリファイルで使用されて、どこからでもスタックスペースを設定し、スタックStack_Memで識別され、末尾にラベル__initial_spを設定します。同様に、ヒープについても、スペースはシンボルHeap_Mem(サイズは0.5kiB)ですが、先頭と末尾にラベルがあります(__heap_baseおよび__heap_limit)。

これらはリンカによって処理され、メモリは(Stack_MemおよびHeap_Memシンボルによって)占有されているため、スタックスペースとヒープスペース内には何も割り当てられませんが、それらのメモリとすべてのグローバルを必要な場所に配置できます。ラベルは、指定されたアドレスで長さのないシンボルになります。__initial_spはリンク時にベクターテーブルに直接使用され、__ heap_baseおよび__heap_limitはランタイムコードによって使用されます。シンボルの実際のアドレスは、リンカーが配置した場所に基づいてリンカーによって割り当てられます。

上記で説明したように、これらのシンボルは実際にはstartup.sファイルから取得する必要はありません。これらはリンカ設定(Keilのスキャッタロードファイル、GNUのリンカスクリプト)から取得でき、それらの場合、配置をよりきめ細かく制御できます。たとえば、スタックをRAMの先頭または末尾に強制的に配置したり、グローバルをヒープから遠ざけたり、必要なものを配置したりできます。HEAPまたはSTACKが、グローバルが配置された後に残っているRAMを占有するように指定することもできます。ただし、他のメモリが減少するほど静的変数を追加することに注意する必要があります。

ただし、各ツールチェーンは異なり、構成ファイルの記述方法と動的メモリアロケータが使用するシンボルは、特定の環境のドキュメントから取得する必要があります。

スタックサイズ

スタックサイズの決定方法については、再帰または関数ポインターを使用しない場合、多くのツールチェーンがプログラムの関数呼び出しツリーを分析することで最大のスタック深度を提供できます。これらを使用する場合、スタックサイズを推定し、カーディナル値を事前に入力し(おそらくmainの前にエントリ関数を使用して)、その後、プログラムがしばらく実行された後、最大深度がどこにあるかを確認します(これはカーディナル値の場所です)終わり)。プログラムを限界まで完全に実行した場合、スタックを縮小できるかどうか、またはプログラムがクラッシュしたり、基本値が残っていない場合は、スタックを増やして再試行する必要があるかどうかをかなり正確に知ることができます。

ヒープサイジング

ヒープサイズの決定は、もう少しアプリケーションに依存します。起動時にのみ動的割り当てを行う場合は、起動コードに必要なスペースを追加できます(さらに、メモリ管理のためのオーバーヘッドも追加できます)。メモリマネージャのソースにアクセスできる場合は、オーバーヘッドが正確にわかります。また、メモリをウォークして使用情報を提供するコードを記述することもできます。動的ランタイムメモリを必要とするアプリケーション(インバウンドイーサネットフレーム用のバッファの割り当てなど)については、スタックサイズを慎重に調整し、スタックおよび静的スタックの後に残っているすべてをヒープに提供することをお勧めします。

最終ノート(RTOS)

OPの質問はベアメタル用にタグ付けされましたが、RTOS用のメモを追加したいと思います。多くの場合(常に?)、各タスク/プロセス/スレッド(簡単にするためにここでタスクを書きます)には、タスクの作成時にスタックサイズが割り当てられます。タスクスタックに加えて、小さなOSもあります。スタック(割り込みなどに使用)

タスクアカウンティング構造とスタックはどこかから割り当てる必要があり、これは多くの場合、アプリケーションの全体的なヒープ領域からのものです。これらのインスタンスでは、OSは初期化時にのみ使用するため、初期スタックサイズは重要ではありません。たとえば、リンク中に残りのすべてのスペースをHEAPに割り当て、ヒープの最後に初期スタックポインターを配置してヒープに成長させ、OSがヒープの先頭から割り当てを開始することを知り、 initial_spスタックを放棄する直前にOSスタックを割り当てます。次に、すべてのスペースが、タスクスタックおよびその他の動的に割り当てられたメモリの割り当てに使用されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.