質問

マルチスレッド埋め込みソフトウェア(CまたはC ++で記述)では、あふれることなく操作を完了できるようにするために、スレッドに十分なスタックスペースを与える必要があります。スタックの正しいサイジングは、一部のリアルタイム埋め込み環境で重要です。これは(少なくとも私が働いたシステムでは)、オペレーティングシステムがこれを検出しないためです。

通常、新しいスレッドのスタックサイズ(メインスレッド以外)は、スレッドが作成された時点で指定されます(つまり、pthread_create()などの引数で)。多くの場合、これらのスタックサイズは、コードが最初に書かれた、またはテストされた時点で良好であることが知られている値にハードコーディングされています。

ただし、コードの将来の変更は、ハードコーディングされたスタックサイズの基礎となる仮定を破ることがよくあり、1つの運命の日には、スレッドがコールグラフのより深い分岐の1つに入り、スタックに溢れ、システム全体を倒します。または静かに破損している記憶。

スレッドで実行されたコードがスタック上のstructインスタンスを宣言する場合、私はこの問題を個人的に見ました。構造体が追加のデータを保持するように拡張されると、スタックサイズがそれに応じて膨張し、潜在的にスタックオーバーフローが発生する可能性があります。これは、構造にフィールドを追加することの完全な効果がすぐにわからないという確立されたコードベースにとって大きな問題になる可能性があると思います(その構造体が使用されるすべての場所を見つけるにはあまりにも多くのスレッド/関数)。

「スタックサイジング」の質問に対する通常の応答は「ポータブルではない」というものであるため、コンパイラ、オペレーティングシステム、およびプロセッサがすべてこの調査のために既知の量であると仮定しましょう。また、再帰が使用されていないと仮定しましょう。そのため、「無限の再帰」シナリオの可能性を扱っていません。

スレッドに必要なスタックサイズを推定する信頼できる方法は何ですか?オフライン(静的分析)と自動の方法を好むが、すべてのアイデアは大歓迎です。

役に立ちましたか?

解決

ランタイム評価

オンライン方法は、0xaaaa(または幅が何であれ0xaa)などの特定の値で完全なスタックをペイントすることです。次に、絵画のどれだけが触れられているかを確認することにより、過去にスタックが最大に成長した大きさを確認できます。

見て これ 説明を使用して説明するリンク。

利点は、それが簡単だということです。欠点は、スタックサイズが最終的にテスト中に使用済みのスタックの量を超えないことを確信できないことです。

静的評価

いくつかの静的チェックがあり、これを行おうとするハッキングされたGCCバージョンさえ存在すると思います。私があなたに言うことができる唯一のことは、一般的なケースでは静的チェックをするのが非常に難しいということです。

また、見てください これ 質問。

他のヒント

のような静的分析ツールを使用できます スタッカナリザー, 、ターゲットが要件に適合している場合。

かなりのお金を使いたい場合は、Klocworkなどの商用静的分析ツールを使用できます。 Klocworkは主にソフトウェアの欠陥とセキュリティの脆弱性を検出することを目的としていますが。ただし、タスクまたはスレッド内のスタックオーバーフローを検出するために使用できる「kwstackoverflow」と呼ばれるツールもあります。私が取り組んでいる組み込みプロジェクトに使用していますが、肯定的な結果が得られました。このようなツールは完璧ではないと思いますが、これらのコマーシャルツールは非常に良いと思います。私が出会ったツールのほとんどは、機能ポインターと闘っています。また、グリーンヒルズのような多くのコンパイラベンダーがコンパイラに同様の機能を構築するようになったことも知っています。これは、おそらくスタックサイズについて正確な決定を下すために必要なすべての詳細について、コンパイラが詳細な知識を持っているため、おそらく最良のソリューションです。

時間があれば、スクリプト言語を使用して独自のスタックオーバーフロー分析ツールを作成できると確信しています。スクリプトは、タスクまたはスレッドのエントリポイントを識別し、完全な関数コールツリーを生成し、各関数が使用するスタック空間の量を計算する必要があります。完全なファンクションコールツリーを生成できるため、おそらく無料のツールが利用できると思われます。スタックスペースを生成するプラットフォームの詳細を知っている場合、各関数が使用するのは非常に簡単です。たとえば、PowerPC関数の最初のアセンブリ命令は、多くの場合、関数に必要な量だけにスタックポインターを調整する更新命令を備えたストアワードです。最初の命令からサイズを正しく取ることができます。これにより、総スタックスペースを比較的簡単に決定できます。

これらのタイプの分析はすべて、スタック使用量の最悪のケース上限の近似を提供します。これはまさに知りたいことです。もちろん、専門家(私が一緒に働いているように)は、スタックスペースをあまりにも割り当てていると不満を言うかもしれませんが、彼らはソフトウェアの品質を気にしない恐竜です:)

もう1つの可能性は、スタックの使用量を計算していませんが、プロセッサのメモリ管理ユニット(MMU)を使用してスタックオーバーフローを検出することです。 PowerPCを使用して、VXWorks 5.4でこれを行いました。アイデアはシンプルです。書き込み保護されたメモリのページをスタックの最上部に配置するだけです。オーバーフローの場合、プロセッサの実行が発生し、スタックオーバーフローの問題にすぐに警告が発生します。もちろん、スタックサイズをどれだけ増やす必要があるかによってはわかりませんが、例外/コアファイルをデバッグすることで良い場合は、少なくともスタックを溢れさせた呼び出しシーケンスを把握できます。その後、この情報を使用して、スタックサイズを適切に増やすことができます。

-djhaus

無料ではありませんが、 カバー性 スタックの静的分析を行います。

静的(オフライン)スタックチェックは、見た目ほど難しくありません。埋め込まれたIDEのために実装しました(Rapiditty) - 現在、ARM7(NXP LPC2XXX)、Cortex-M3(STM32およびNXP LPC17XX)、X86、および社内MIPS ISA互換FPGAソフトコアで動作しています。

基本的に、実行可能性コードの単純な解析を使用して、各関数のスタック使用量を決定します。最も重要なスタック割り当ては、各関数の開始時に行われます。さまざまな最適化レベルでどのように変化するか、該当する場合はアーム/親指の命令セットなどを確認してください。タスクには通常独自のスタックがあり、ISRは(常にではない)個別のスタック領域を共有することが多いことを忘れないでください。

各関数の使用法が得られると、解析からコールツリーを構築し、すべての関数の最大使用法を計算するのはかなり簡単です。私たちのIDEは、スケジューラー(効果的な薄いRTOS)を生成するため、どの機能が「タスク」として指定され、どの機能がISRであるかを正確に知っているため、各スタック領域の最悪の使用法を知ることができます。

もちろん、これらの数字はほとんど常に 実際 最大。のような関数を考えてください sprintf それはaを使用できます 多く スタックスペースのものですが、提供する形式の文字列とパラメーターによって大きく異なります。これらの状況では、動的分析を使用することもできます。スタートアップの既知の値でスタックを埋めてから、デバッガーでしばらく実行して、各スタックのどれだけがあなたの値で満たされているかを確認することができます(高いウォーターマークスタイルのテスト) 。

どちらのアプローチも完璧ではありませんが、両方を組み合わせることで、実際の使用法がどのようになるかについてかなり良い写真が得られます。

答えで説明したように この質問, 、一般的な手法は、既知の値でスタックを初期化し、しばらくコードを実行してパターンが停止する場所を確認することです。

これはオフラインの方法ではありませんが、私が取り組んでいるプロジェクトでは、アプリケーション内のすべてのタスクスタックの高い水マークを読み取るデバッグコマンドがあります。これにより、各タスクのスタック使用量のテーブルと、利用可能なヘッドルームの量が出力されます。多くのユーザーインタラクションで24時間の実行後にこのデータをチェックすると、定義されたスタック割り当てが「安全」であるという自信があります。

これは、スタックを既知のパターンで埋めるためのよく試された手法を使用して機能し、これを書き直すことができる唯一の方法は通常のスタックの使用法であると仮定しますが、それが他の手段によって書かれている場合、スタックオーバーフローは少なくともあなたの心配!

私たちは、私の仕事で埋め込まれたシステムでこの問題を解決しようとしました。それは夢中になりました、信頼できる答えを得るには、あまりにも多くのコード(独自のパーティーフレームワークとサードパーティのフレームワークの両方)があります。幸いなことに、当社のデバイスはLinuxベースであったため、すべてのスレッド2MBに与えて仮想メモリマネージャーに使用を最適化できるようにする標準動作に戻りました。

このソリューションに関する私たちの1つの問題は、サードパーティのツールの1つが実行されたことでした。 mlock メモリスペース全体(パフォーマンスを改善するのは理想的です)。これにより、スレッドの各スレッド(75-150)の2MBのスタックがすべてページングされました。メモリスペースの半分を失い、違反ラインをコメントするまで失いました。

サイドノート:Linuxの仮想メモリマネージャー(VMM)は、4KチャンクでRAMを割り当てます。新しいスレッドが2MBのアドレススペースをスタック用に要求すると、VMMはBogusメモリページを最もトップのページ以外のすべてに割り当てます。スタックが偽のページに成長すると、カーネルはページ障害を検出し、偽のページを実際のページ(実際の4kを消費する)と交換します。これにより、スレッドのスタックが必要な任意のサイズ(2MB未満である限り)に成長し、VMMは最小限のメモリのみが使用されることを保証します。

すでに行われた提案のいくつかとは別に、スタックのサイズを妥当なサイズに保つ必要があるため、スタックの使用法を強く制御する必要があることが多いことを指摘したいと思います。

ある意味では、スタックスペースを使用することは、メモリを割り当てることに少し似ていますが、(簡単な)割り当てが成功したかどうかを判断するための(簡単な)方法はありません。したがって、たとえば、システムがスタックからローカル変数のメモリを割り当てる場合、そのメモリをmalloc()に割り当てるか、malloc()を使用できない場合、独自のメモリハンドラーを作成します(これは十分に単純なタスクです)。

いいえ:

void func(myMassiveStruct_t par)
{
  myMassiveStruct_t tmpVar;
}

はいはい:

void func (myMassiveStruct_t *par)
{
  myMassiveStruct_t *tmpVar;
  tmpVar = (myMassiveStruct_t*) malloc (sizeof(myMassicveStruct_t));
}

かなり明白に思えますが、しばしばそうではありません - 特にmalloc()を使用できない場合。

もちろん、あなたはまだ問題を抱えているので、これはただの助けになるものですが、あなたの問題を解決しません。ただし、スタックに適したサイズを見つけたら、コードの変更の後、スタックスペースを再び使い果たした場合、スタックのサイズを見つけたら、将来スタックサイズを見積もるのに役立ちます。その他の問題(1つには深すぎるコールスタック)。

100%確信はありませんが、これも行われる可能性があると思います。 JTAGポートが露出している場合は、trace32に接続して、最大スタック使用量を確認できます。このためには、最初のかなり大きな任意のスタックサイズを与える必要があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top