8

問題

.Net 4.5を使用してWindows 7 ベースの C# WPFアプリケーションを作成しています。その主な機能の 1 つは、一連のユーザー定義のサイクル時間でカスタム ハードウェアとやり取りする特定の関数を呼び出すことです。たとえば、ユーザーは 2 つの関数を 10 または 20 ミリ秒ごとに呼び出し、別の関数を 500 ミリ秒ごとに呼び出すように選択できます。ユーザーが選択できる最小のサイクル時間は 1 ミリ秒です。

最初は、タイミングが正確で、必要に応じて 1 ミリ秒ごとに関数が呼び出されたように見えました。しかし、後で、約1 ~ 2%のタイミングが正確ではないことに気付きました。一部の関数はわずか 5 ミリ秒遅れて呼び出され、他の関数は最大 100 ミリ秒遅れて呼び出されました。サイクル タイムが 1 ミリ秒を超えていても、外部関数を呼び出すべき時間にスレッドがスリープするという問題に直面しました (スレッドがスリープ状態で関数を呼び出さなかったために、20 ミリ秒の関数が 50 ミリ秒遅れて呼び出される可能性があります)。

分析の結果、これらの遅延は散発的であり、目立ったパターンはなく、これらの遅延の背後にある主な原因は OS のスケジューリングとスレッド コンテキストの切り替えであると結論付けました。 .

Windows 7 は RTOS ではないため、この問題を回避できるかどうかを調べる必要があります。ただし、最大 0.7 ミリ秒のエラー許容範囲でタイミング制約を満たすことができる同様の機能を持つ他のツールを使用しているため、この問題が Windows で修正可能であることは確かです。

私たちのアプリケーションはマルチスレッド化されており、同時に最大約 30 のスレッドが実行されています。現在のピーク CPU 使用率は約 13% です。

試みられた解決策

さまざまなことを試しましたが、タイミングは主にストップウォッチ タイマーを使用して測定され、IsHighResolutionは true でした (他のタイマーが使用されましたが、大きな違いはわかりませんでした)。

  1. 別のスレッドを作成し、
    それに高い優先度を与えるThread.Sleep()

  2. C# タスク (スレッド プール) を使用する
    結果:ほとんど改善されない

  3. 1 ミリ秒の周期性を持つマルチメディア タイマーを使用する
    結果:効果がないか、さらに悪いことに、マルチメディア タイマーは OS を起動する際には正確ですが、OS は別のスレッドを実行することを選択する可能性があり、1 ミリ秒の保証はありませんが、それでも遅延が時折大きくなる可能性があります

  4. while ループとストップウォッチ タイマーだけを含む別のスタンドアロン C# プロジェクトを作成しまし

  5. ポイント 4 を繰り返しましたが、プロセスの優先度を Realtime/High に設定し
    まし

結論:

前回から、5 つの可能な行動方針があることがわかりましたが、正しい方向に向けるためには、そのような問題の経験を持つ知識のある人が必要です。

  1. ツールを最適化し、スレッドを何らかの方法で管理して、1 ミリ秒のリアルタイム要件を保証することができます。最適化の一部として、ツールのプロセス優先度を高またはリアルタイムに設定している可能性がありますが、ユーザーが他のいくつかのツールを同時に使用している可能性があるため、これは賢明な決定とは思えません。

  2. ツールを 2 つのプロセスに分割します。1 つは GUI とすべての非タイム クリティカルな操作を含み、もう 1 つは最小限のタイム クリティカルな操作を含み、高/リアルタイム優先度に設定し、IPC (WCF など) を使用してプロセス間の通信。これは2つの点で私たちに利益をもたらす可能性があります

    1. 操作がはるかに少ないため、他のプロセスが枯渇する可能性が低くなります。

    2. プロセスのスレッドが少ないため、スレッドがスリープする可能性が (はるかに低いか、まったくない)

注: 次の 2 つのポイントでは、カーネル スペースについて説明します。カーネル スペースとドライバーの作成に関する情報はほとんどないため、カーネル スペースの使用方法について間違った仮定をしている可能性があることに注意してください。

  1. 1 ミリ秒ごとに低レベルの割り込みを使用して、スレッドがプロセス内で指定されたタスクを強制的に実行するイベントを発生させるドライバーをカーネル空間に作成します。

  2. タイム クリティカルなコンポーネントをカーネル スペースに移動し、プログラムの本体とのインターフェイスは、API とコールバックを介して行うことができます。

  3. おそらくこれらはすべて有効ではなく、IntervalZero RTOS プラットフォームのような Windows RTOS 拡張機能を使用する必要があるのでしょうか?

質問自体

私が探している答えは 2 つあります。良い情報源に裏付けられていることを願っています。

  1. これは本当にスレッドとコンテキストの切り替えの問題ですか? それとも、ずっと何かを見逃していたのでしょうか?

  2. この問題を確実に解決できる 5 つのオプションはどれですか。これらのオプションのどれもそれを修正できない場合、何ができるでしょうか? 私たちがベンチマークした他のツールは、実際に Windows で必要なタイミング精度に達していることを覚えておいてください。CPU の負荷が高い場合、100,000 のうち 1 つまたは 2 つのタイミングが 2 ミリ秒未満ずれている可能性がありますが、これは非常に許容範囲です。

4

2 に答える 2

7

5 つのオプションのうち、この問題を確実に解決できるのはどれですか?

これは、達成しようとしている精度によって異なります。たとえば +/- 1ms を目指している場合、ポイント 3) から 5) なしでそれを達成する合理的なチャンスがあります。ポイント1)と2)の組み合わせが進むべき道です:

  • コードを時間的に重要な部分と時間的に重要でない部分 (GUI など) に分割し、それらを別々のプロセスに入れます。適切な IPC (パイプ、共有メモリなど) を使用して通信させます。
  • タイム クリティカルなプロセスのプロセス優先度クラスとスレッド優先度を上げます。残念ながら、c# ThreadPriority EnumerationTHREAD_PRIORITY_HIGHEST(2)は最大優先度としてのみ許可されます。したがって、へのアクセスを許可するSetThreadPriority関数を調べる必要がありますTHREAD_PRIORITY_TIME_CRITICAL (15)Process::PriorityClass プロパティは へのアクセスを許可しますREALTIME_PRIORITY_CLASS (24)。注: このような優先順位でコードを実行すると、他のすべてのコードが押し出されます。非常に少ない計算で非常に安全なコードを作成する必要があります。
  • ProcessThread::ProcessorAffinityプロパティを使用して、適切なコアの使用を調整します。ヒント: Windows カーネルは特定の操作にこの CPU を優先するため、タイム クリティカルなスレッドを CPU_0 (プロパティ値 0x0001) から遠ざけることができます。例: 4 つの論理プロセッサを備えたプラットフォームでは、ProcessoreAffinity プロパティを 0x000E で指定して、CPU_0 を除外します。
  • システム タイマーの解像度は、多くの場合、他のアプリケーションによって設定されます。したがって、システム タイマーの解像度を指示する場合にのみ予測可能です。一部のアプリケーション/ドライバーでは、タイマーの分解能が 0.5ms に設定されています。これは設定を超えている可能性があり、アプリケーションで問題が発生する可能性があります。タイマーの分解能を 0.5ms に設定する方法については、このSO の回答を参照してください。(注: この解像度のサポートは、プラットフォームに依存します。)

一般的な注意事項: すべて負荷に依存します。Windows は「リアルタイム OS」ではないにもかかわらず、かなりうまく機能します。ただし、リアルタイム システムも低負荷に依存しています。負荷が高い場合の RT-OS でも、何も保証されません。

于 2016-07-28T13:15:24.083 に答える
4

ユーザーモードで、スレッドの優先度またはアフィニティに対して何もしなくても、求める動作が保証されるとは思わないので、カーネルモードドライバーを作成することを意味するオプション 3 または 4 のようなものが必要になると思います。

カーネル モードでは、IRQL の概念があり、より高いレベルで実行するようにトリガーされたコードが、より低いレベルで実行されているコードより優先されます。ユーザー モード コードは IRQL 0 で実行されるため、より高いレベルのすべてのカーネル モード コードが優先されます。スレッド スケジューラ自体は昇格したレベルで実行されるため、REALTIME_PRIORITY_CLASS を含む、任意の優先度のスケジュールされたユーザー モード コードをプリエンプトできます。タイマーを含むハードウェア割り込みは、さらに高速で実行されます。

ハードウェア タイマーは、より低い IRQL で使用可能な CPU/コアがある場合 (高レベルの割り込みハンドラーが実行されていない場合)、タイマーの解像度とほぼ同じくらい正確に割り込みハンドラーを呼び出します。

やるべき作業が多い場合は、割り込みハンドラー (IRQL > DISPATCH_LEVEL) でそれを行うべきではありませんが、割り込みハンドラーを使用して、遅延プロシージャ呼び出し ( DPC )、スレッド スケジューラが干渉するのを防ぎますが、他の割り込みハンドラーがハードウェア割り込みを処理するのを防ぎません。

オプション 3 で発生する可能性のある問題は、IRQL 0 でユーザー モード コードを実行するためにスレッドを起動するイベントを発生させると、スレッド スケジューラがユーザー モード コードをいつ実行するかを決定できるようになることです。DISPATCH_LEVEL のカーネル モードで、時間に制約のある作業を行う必要がある場合があります。

もう 1 つの問題は、CPU コアが実行していたプロセス コンテキストに関係なく、割り込みが発生することです。したがって、タイマーが起動すると、ハンドラーはおそらくあなたのものとは関係のないプロセスのコンテキストで実行されます。そのため、プロセスとは無関係に、カーネル空間メモリを使用してカーネルモード ドライバーで時間に敏感な作業を行い、後でアプリが実行を再開してドライバーと対話できるようになったときに、結果をアプリにフィードバックする必要がある場合があります。 . (アプリは、DeviceIoControl API を介してバッファーを渡すことにより、ドライバーと対話できます。)

ハードウェア タイマー割り込みハンドラーを実装することをお勧めしているわけではありません。OSはすでにそれを行っています。代わりに、カーネル タイマー サービスを使用して、タイマー割り込みの OS 処理に基づいてコードを呼び出します。KeSetTimerおよびExSetTimerを参照してください。これらはどちらも、タイマーが起動した後、DISPATCH_LEVEL でコードにコールバックできます。

また、(カーネル モードであっても) システム タイマーの解像度は、既定では 1 ミリ秒の要件に対して粗すぎる場合があります。

https://msdn.microsoft.com/en-us/library/windows/hardware/dn265247(v=vs.85).aspx

たとえば、x86 プロセッサで実行されている Windows の場合、システム クロック ティック間のデフォルトの間隔は通常約 15 ミリ秒です。

より高い解像度を得るには、

  1. システムクロックの解像度を変更する

Windows 2000 以降、ドライバーは ExSetTimerResolution ルーチンを呼び出して、連続するシステム クロック割り込み間の時間間隔を変更できます。たとえば、ドライバーはこのルーチンを呼び出して、システム クロックを既定のレートから最大レートに変更し、タイマーの精度を向上させることができます。ただし、ExSetTimerResolution の使用には、ExAllocateTimer によって作成された高解像度タイマーの使用と比較して、いくつかの欠点があります。

...

  1. クロック解像度を自動的に管理する高解像度タイマーには、新しいカーネルモード API を使用します。

Windows 8.1 以降では、ドライバーは ExXxxTimer ルーチンを使用して高解像度タイマーを管理できます。高解像度タイマーの精度は、サポートされているシステム クロックの最大解像度によってのみ制限されます。対照的に、既定のシステム クロックの解像度に制限されているタイマーは、精度が大幅に低下します。

ただし、高精度のタイマーでは、システム クロックの割り込みが (少なくとも一時的に) 高速で発生する必要があるため、消費電力が増加する傾向があります。したがって、ドライバーは、タイマーの精度が不可欠な場合にのみ高解像度のタイマーを使用し、それ以外の場合は既定の解像度のタイマーを使用する必要があります。

于 2016-07-29T17:45:11.933 に答える