RTOSのタイマーの質問へ

ここに2つの行があります。私は天才です。

熱意、月桂樹、花を与えます...



この投稿は、タイマーを読むというかなり古いタスクに捧げられています。これは、非同期デバイスでのレースとの戦いについて議論しているJack Gansleyの本(The Art of Designing Embedded Systems(Second Edition)、(2008)by Jack Ganssle)で個人的にレビューしたものです。 問題が定式化され、それを解決する4つの方法(2つの不正解と2つの正解)が示され、それらの欠点は一般に、ジャックスタイルの堅実な作業と見なされます(私は非常によく関連しています)。 残念ながら、私の意見では、実用的なソリューションでさえ適切な優雅さはありませんでしたが、より美しいものは長い間存在しませんでした、そして昨日それは私に夜明けしました。 ですから、私は非常にエレガントな解決策を思いついたので、この問題をその歴史的な文脈で述べる権利があると考えています(あなたは自分を賞賛することはありません、あなたは唾を吐くように一日中行きます)。



問題を定式化します。特定のシステム周波数から同期され、時間の測定に使用できるハードウェアカウンターがあります。 ただし、ハードウェアの制限により、その容量は長期間の形成には十分ではないため、カウンター容量のソフトウェア拡張が作成されます。 次のように実装されます-カウンターがオーバーフローすると、割り込みが発生し、割り込み処理ルーチンがグローバル変数を変更します。グローバル変数は、カウンター自体と一緒に延長時間カウンターを形成します。
unsigned int High; interrupt TimerOv(void) { High++; }
      
      



その後、次のように現在の時刻を書くことができます
  unsigned long int GetTimer(void) { return (High<<sizeof(int))+ ReadReg(TimerCounter); }
      
      



あなたが期待するように、すべてがシンプルで、明確で、間違っています。 ここで問題は表面にあり、そのようなプログラムを書いたすべての人に見えます-拡張タイマーの2つの半分を読み取るプロセスでは、若い部分がオーバーフローする可能性があります。非同期であるため、2つの部分は互いに対して有効ではありません。 同時に、拡張タイマーの値として過去の瞬間と未来の両方を取得できますが、これらのケースはどちらも悪いです。



この問題には明らかな解決策があります-読み取り時にタイマーのハードウェアを停止しますが、そのような解決策はカウントされた時間への影響により受け入れられません。



別の明らかな解決策を試してみましょう-割り込み読み取り時間を禁止すると、
  unsigned long int GetTimer(void) { DisableInt(); unsigned long Tmp=(High<<sizeof(int))+ ReadReg(TimerCounter); EnableInt(); return Tmp; }
      
      



もちろん、中断の禁止について書いているとき、それは最後に復元することで現在の値を保存することを意味しますが、これらの微妙な点を省きます、それらは今それほど重要ではありません。 このソリューションで良くないこと:1)中断を禁止しますが、これは好ましくありません。2)このソリューションは機能しません。 はい、そうです。メソッドはテスト済みですが、この場合はそうではありません。 実際には、割り込みの禁止によりタイマーが禁止されるため(ハードウェアです)、したがって、割り込み要求期間中にカウンターがオーバーフローすると、若い部分がゼロになり、古い部分は変更されません。つまり、データは無効になります。



このソリューションの可能な変更は、次のコードにつながります
  unsigned long int GetTimer(void) { DisableInt(); unsigned long TmpH=High; unsigned long TmpL=ReadReg(TimerCounter); if (TimerOvBit()) { TmpH++; TmpL=ReadReg(TimerCounter); }; EnableInt(); TmpH=(TmpH<<sizeof(int))+ TmpL; return Tmp; }
      
      



これが最初の正しい決定です。 このソリューションには欠点があります:1)まだ割り込みを禁止しています、2)追加のハードウェアビットが必要であり、割り込みを処理する際にそれを処理する必要があります、3)古い部分の変更について特定の仮定をしました。 他の解決策はありますか?



はい、同様の解決策が存在し、ジャックと私が(正直に、独立して)見つけました。 このソリューションのコードは次のとおりです
  unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; do { TmpH=High; TmpL= ReadReg(TimerCounter); while (TmpH!=High); return (TmpH<<sizeof(int))+TmpL; }
      
      



これは2番目の正しい決定です。カウンターの読み取りは古い部分への呼び出しによってフレーム化されるという事実に注意を払いましょう。そうでない場合は間違っています。 一般に、このアプローチは、私が何かで好きなリソースと競合する場合、ノンブロッキングアルゴリズムに非常によく似ており、いくつかの優雅さがあります。 この方法には多くの利点がありますが、1つの欠点は、システムの負荷が高い場合、Jackが書いているように、実行時間が予測不能になるため、サイクル内で長時間ハングする可能性があることです。



そしてここで、このアルゴリズムの小さな修正が発明されました。
  unsigned long int GetTimer(void) { unsigned long TmpH,TmpL; TmpH=High; TmpL= ReadReg(TimerCounter); if (TmpH!=(TmpH=High)) TmpL= ReadReg(TimerCounter); return (TmpH<<sizeof(int))+TmpL; }
      
      



これは3番目の正しい決定であり、著者として私はそれが最も好きです。 中断を禁止せず、機器に何も必要とせず、仮定を行わず、常に正しい結果を取得します。つまり、1)プロシージャに入る瞬間に先行する拡張タイマーの値を取得することはありません。2)終了する瞬間に値を取得することはありません手順、完全に予測可能な動作があり、これはすべて無料です。 もちろん、負荷の高いシステムがある場合、プロシージャを終了する時間よりも大幅に短い時間を取得できますが、これは他の2つの正しい方法の特徴でもあります。 最小の偏差で現在の時間の値が本当に必要な場合、禁止された割り込みですべての処理を実行する必要があり、これは明らかに私たちの場合ではありません。



私がコメントで正しく指摘されたように、コンパイラはアルゴリズムの重要な行を誤ってコンパイルする可能性があるため、ここに修正されたバージョンがあります
  unsigned long int GetTimer(void) { unsigned long TmpHFirst,TmpH,TmpL; TmpHFirst=High; TmpL= ReadReg(TimerCounter); if (TmpHFirst!=(TmpH=High)) TmpL= ReadReg(TimerCounter); return (TmpH<<sizeof(int))+TmpL; }
      
      





以前にこの解決策が見つからなかった理由(文献でも解決しなかったかもしれませんが、たぶん出くわしなかったのかもしれません)は、完全に理解できません。 なぜそれが機能するのか、私はそれを検討するために読者に任せますが、確かに機能します、反例を見つけることができませんでした。



結論として、別の興味深い点があります。 私たちは時間に興味がありません。そのため、通常は現在の時間を使用して、それに関連する将来のポイントを設定し、それに到達したら何かをします。 だから、あなたの意見では、次の2行は同等ですか?
  unsigned long TmpWait; TmpWait=GetTimer()+SomeDelay; while (TmpWait > GetTimer()) ; /*   */ while ((TmpWait - GetTimer()) > 0) ; /*  */ }
      
      



これらの行は特定の条件下では同等ではなく、その理由を検討することは興味深いです。 興味があれば、Linuxマニュアルを送信します。jiffiesという用語に注目してください。



All Articles