4

大学のリアルタイム システム コースで ARM マイクロコントローラを使用しています。現在取り組んでいるプロジェクトでは、ベクトル フィールド ヒストグラム (VFH) アルゴリズムを実装しています。

問題は次のとおりです。スレッド間で通信する必要があります。より具体的には、距離計からセンサー データを取得し、必要な変換を行い、それらをキューに入れるスレッドが必要です。それら、別のスレッドがこのデータを取得して処理する必要があります。

現時点では、より単純なバージョンを使用しています。1 つのスレッドが ADC からデータを取得し (SensorAcquisitionHandler)、別のスレッドが最初の 5 項目 (最大) の平均をディスプレイ (ControlSignalHandler) に出力します。

/* Queue used to store data from the rangefinder sensors. */
static unsigned int Sensors[100];
static int SensorsHead = 0;
static int SensorsTail = 0;

void SensorAcquisitionHandler(void) {
    /* Clear the interrupt. */
    ADCIntClear(ADC0_BASE, 1);

    int i; /* Index for the measurements buffer. */

    /* There are only 3 rangefinders used. */
    if (ADCSequenceDataGet(ADC0_BASE, 1, rangeBuffer) == 3) {
        /* Put rangeBuffer's data into SensorDataQueue. */
        /* Also, when using SensorDataQueue, must put what's the direction of 
        the corresponding range measurement. */

        /* Critical section ahead!!! Turn off interrupts!!! */
        IntMasterDisable();

        /* Temporarily using the simple FIFO... */
        for (i = 0; i < 3; ++i) {
            if (SensorsHead < 100) {
                Sensors[SensorsHead] = rangeBuffer[i];
                SensorsHead++;
            }
        }

        /* All is fine, turn on interrupts. */
        IntMasterEnable();
    }
}

void ControlSignalHandler(void) {
    /* Clear the timer interrupt. */
    TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);

    unsigned char i; /* Index for the measurements buffer. */
    unsigned long average = 0;
    char buffer[20];

    /* Average first n (n <= 5) elements from Sensors queue. */
    for (i = 0; i < 5 && SensorsTail < SensorsHead; ++i) {
        average += Sensors[SensorsTail];
        SensorsTail++;
    }

    IntMasterDisable();
    average /= i;

    sprintf(buffer, "%d  ", average);

    average = 0;

    if (SensorsTail >= SensorsHead) {
        SensorsTail = 0;
        SensorsHead = 0;
    }

    Display96x16x1StringDraw(buffer, 0, 0);
    IntMasterEnable();
}

結果はしばらくの間比較的安定していますが、ランダムな間隔で非常に高くなります (結果はほぼ常に ~330 です)。また、「非常に高い値」の瞬間にシンボリック デバッガーを使用すると、SensorTail と SensorHead のインデックスが 300 以上になることがあります (キューは 100 要素の配列です)。

これはある種のオーバーフローのように聞こえますが、それがどのように起こっているかを視覚化することはできません. 誰かがそれを見つけるのを手伝ってくれますか?

問題に対する答えは「スレッドセーフなキューを使用する」ことだとわかっていますが、ここで競合状態がどのように発生しているか、インデックスがどのように混乱しているかなどを理解したいと思います。ありがとう!

4

4 に答える 4

3

割り込みをクリアすることにより、それが再び発生することを許可しています。ControlSignalHandler(たとえば)タイマーがコードを追い越したために、最初のループの途中で再入力された場合にどうなるか想像してみてください...

関数全体(両方)をIntMasterDisable+でラップしIntMasterEnable、無効化後と有効化の前に割り込みをクリアします。(有効にする直前に実行します。)

于 2012-10-25T23:16:46.883 に答える
3

ロックレスのシングルリーダーシングルライターFIFOを使用することで、ヘッドポインターとテールポインターの競合状態を回避できます。これは、ヘッドポインターが1つのスレッド(またはあなたの場合はISR)でのみ書き込まれ、テールが書き込まれるFIFOです。もう一方。これは、各 ISR でバッファ ラッピングのテストを実行することを意味します。

これを行って、各 ISR の最後に割り込みソースをリセットした場合、ロックはまったく必要ありません。割り込みをグローバルに無効にすることは非常に悪いマナーです。現在、ロックを長時間保持しています。

FIFO 実装を書き直す必要があるもう 1 つの理由は次のとおりです。

    for (i = 0; i < 3; ++i) {
        if (SensorsHead < 100) {

一度に 3 つの読み取り値を追加しているので、最終的には で入力SensorAcquisitionHandler()SensorsHead==99ます。これにより、2 つの読み取り値を破棄することが保証されます。

同様に:

/* Average first n (n <= 5) elements from Sensors queue. */
for (i = 0; i < 5 && SensorsTail < SensorsHead; ++i) {
    average += Sensors[SensorsTail];
    SensorsTail++;
}

場合によっては、5 つ未満の値で計算を実行します。

使用している ARM パーツによっては、ハードウェア分割がありません。2 のべき乗の値で平均を計算すると、1 サイクルの論理シフトであるため、はるかに安価になります。

最後に、これDisplay96x16x1StringDraw(buffer, 0, 0);は特にコストのかかる操作であり、スレッドセーフでもあると思います。IO は、ISR では常に厳密に禁止されています。おそらく、タイマー スレッドと非割り込みコンテキスト (出力を処理する) の間に別のキューが必要です。

于 2012-10-26T00:03:14.053 に答える
2

どの特定のプロセッサ/マイクロコントローラを使用していますか? どの RTOS (ある場合)?

多くの場合、ARM マイクロコントローラの割り込みスタックは非常に小さいです。のようなランタイム ルーチンsnprintf()は、簡単に数百バイトを必要とし、小さなスタックをオーバーランする可能性があります。スタック スペースの考慮事項はさておき、C ランタイム関数は多くの場合、割り込みコンテキストで安全に使用できません。多くの場合、割り込みから呼び出すことができる関数について非常に制限されています。詳細は、使用している実際の RTOS とコンパイラ ツールチェーンによって異なります。

これらの制約に違反すると、データが破損しやすくなります。

于 2012-10-25T23:24:41.763 に答える
0

アセンブリ ダンプを見てください。不揮発性コードは、揮発性コードに関して並べ替えることができます。

あなたが持っていると想像してください:

assignmentA_with_volatile_operands;
assignmentB_with_non_volatile_operands;
assignmentC_with_volatile_operands;

コンパイルは、これを次のように自由に並べ替えることができます。

assignmentA_with_volatile_operands;
assignmentC_with_volatile_operands;
assignmentB_with_non_volatile_operands;

たとえば、ループに表示されるオブジェクトが修飾されていないforため、最初のハンドラーのループをSensorAcquisitionHandler実際に実行できます。IntMasterEnableforvolatile

編集:

この種のコードの並べ替えは許可されていないと考える人もいます。実際、それらは実際のコンパイラで実行されます。

volatile は、プログラム内でメモリ バリアとして機能しません。揮発性アクセスが不揮発性アクセスに関してメモリバリアとして機能すると想定しないでください。

標準では、C11 の 5.1.2.3で、プログラムの観察可能な動作に関して適合する実装に関する最小要件とは何かを定義しています。

gccたとえば、次のように述べています。不揮発性オブジェクトへのアクセスは、揮発性アクセスに関して順序付けされていません。volatile オブジェクトをメモリ バリアとして使用して、不揮発性メモリへの一連の書き込みを順序付けることはできません。

http://gcc.gnu.org/onlinedocs/gcc/Volatiles.html

多くのコンパイラは、揮発性の存在下でこの種のコードの並べ替えの最適化を実行しないように注意していますが、コンパイラ (gccたとえば) がそれらを実行しているのを見たことがあります。

于 2012-10-25T23:02:41.273 に答える