スピン ロックの使用: 例
ドライバーがスピン ロックを保持する時間を最小限に抑えることで、ドライバーのパフォーマンスとシステム全体の両方のパフォーマンスを大幅に向上させることができます。 たとえば、次の図は、割り込みスピン ロックによって、ISR と SMP マシンの StartIo ルーチンおよび DpcForIsr ルーチンの間で共有する必要があるデバイス固有のデータを保護する方法を示しています。
ドライバーの ISR は 1 つのプロセッサで DIRQL で実行されますが、その StartIo ルーチンは 2 番目のプロセッサ上の DISPATCH_LEVEL で実行されます。 カーネル割り込みハンドラーは、ドライバーの ISR の InterruptSpinLock を保持します。これは、ドライバーのデバイス拡張機能で、状態やデバイス レジスタ (SynchronizeContext) へのポインターなど、保護されたデバイス固有のデータにアクセスします。 SynchronizeContext にアクセスする準備ができている StartIo ルーチンは、KeSynchronizeExecution を呼び出し、関連付けられている割り込みオブジェクトへのポインター、共有 SynchronizeContext、およびドライバーの SynchCritSection ルーチン (前の図の AccessDevice) を渡します。
ISR が戻るまで、ドライバーの InterruptSpinLock を解放すると、KeSynchronizeExecution スピンは 2 番目のプロセッサで実行され、AccessDevice が SynchronizeContext に触れないようにします。 ただし、 KeSynchronizeExecution では、2 番目のプロセッサの IRQL を割り込みオブジェクトの SynchronizeIrql に上げることで、ISR が戻るとすぐに AccessDevice を DIRQL で実行できるようになり、別のデバイス割り込みがそのプロセッサで発生するのを防ぎます。 しかし、他のデバイスに対するより高い DIRQL 割り込み、クロック割り込み、および電源障害割り込みは、どちらのプロセッサでも発生する可能性があります。
ISR がドライバーの DpcForIsr をキューに入れ、返すと、AccessDevice は、関連付けられている割り込みオブジェクトの SynchronizeIrql で 2 番目のプロセッサで実行され、SynchronizeContext にアクセスします。 一方、 DpcForIsr は、DISPATCH_LEVEL IRQL の別のプロセッサで実行されます。 DpcForIsr は SynchronizeContext にアクセスする準備もできるので、StartIo ルーチンが手順 1 で行ったのと同じパラメーターを使用して KeSynchronizeExecution を呼び出します。
KeSynchronizeExecution がスピン ロックを取得し、StartIo ルーチンの代わりに AccessDevice を実行すると、ドライバーが提供する同期ルーチン AccessDevice に SynchronizeContext への排他的アクセスが付与されます。 AccessDevice は SynchronizeIrql で実行されるため、AccessDevice の実行中にデバイスが別のプロセッサで割り込んだ場合でも、スピン ロックが解除されるまで、ドライバーの ISR はスピン ロックを取得して同じ領域にアクセスできません。
AccessDevice が戻り、スピン ロックが解放されます。 StartIo ルーチンは、2 番目のプロセッサの DISPATCH_LEVEL で実行を再開します。 KeSynchronizeExecution は 3 番目のプロセッサで AccessDevice を実行するため、DpcForIsr の代わりに SynchronizeContext にアクセスできるようになりました。 ただし、手順 2 で DpcForIsr が KeSynchronizeExecution を呼び出す前にデバイス割り込みが発生した場合、KeSynchronizeExecution がスピン ロックを取得し、3 番目のプロセッサで AccessDevice を実行する前に、ISR が別のプロセッサで実行される可能性があります。
前の図に示すように、1 つのプロセッサで実行されているルーチンはスピン ロックを保持していますが、そのスピン ロックを取得しようとする他のすべてのルーチンは作業を行いません。 既に保持されているスピン ロックを取得しようとする各ルーチンは、ホルダーがスピン ロックを解放するまで、現在のプロセッサ上でスピンするだけです。 スピン ロックが解放されると、1 つのルーチンだけがそれを取得できます。同じスピン ロックを取得しようとしている他のすべてのルーチンは、スピンし続けます。
スピン ロックの保持者は、発生した IRQL で実行されます。エグゼクティブ スピン ロックの場合は DISPATCH_LEVEL で、割り込みスピン ロックの場合は DIRQL で実行されます。 KeAcquireSpinLock と KeAcquireInStackQueuedSpinLock の呼び出し元は、KeReleaseSpinLock または KeReleaseInStackQueuedSpinLock を呼び出してロックを解除するまで、DISPATCH_LEVELで実行されます。 KeSynchronizeExecution の呼び出し元は、呼び出し元が指定した SynchCritSection ルーチンが終了し、KeSynchronizeExecution が制御を返すまで、割り込みオブジェクトの SynchronizeIrql に現在のプロセッサの IRQL を自動的に発生させます。 詳細については、「スピン ロックを使用するサポート ルーチンの呼び出し」を参照してください。
スピン ロックの使用については、次の点に注意してください。
より低い IRQL で実行されるすべてのコードは、スピン ロック 保持者と同じスピン ロックを取得しようとしている他のルーチンによって占有されているプロセッサのセットで作業を行うことはできません。
その結果、ドライバーがスピン ロックを保持する時間を最小限に抑えることで、ドライバーのパフォーマンスが大幅に向上し、システム全体のパフォーマンスが大幅に向上します。
前の図に示すように、カーネル割り込みハンドラーは、先着順でマルチプロセッサ コンピューター内の同じ IRQL で実行されるルーチンを実行します。 カーネルは次のようなことも行います。
ドライバー ルーチンが KeSynchronizeExecution を呼び出すと、カーネルは、KeSynchronizeExecution への呼び出しが発生したのと同じプロセッサでドライバーの SynchCritSection ルーチンを実行します (手順 1 と 3 を参照)。
ドライバーの ISR が DpcForIsr をキューイングすると、カーネルは、IRQL が DISPATCH_LEVEL を下回る最初の使用可能なプロセッサで DPC を実行させます。 これは、IoRequestDpc 呼び出しの発生元と同じプロセッサであるとは限りません (手順 2 を参照)。
ドライバーの割り込み駆動型 I/O 操作は、ユニプロセッサ コンピューターでシリアル化される傾向がありますが、SMP コンピューターでは同じ操作が、まさに非同期になります。 前の図に示すように、ドライバーの ISR は、DpcForIsr が既に CPU1 のデバイス割り込みを処理している IRP の処理を開始する前に、SMP コンピューターの CPU4 で実行できます。
つまり、DpcForIsr ルーチンまたは CustomDpc ルーチンが実行される前に別のプロセッサでデバイス割り込みが発生したときに、割り込みスピン ロックが、あるプロセッサ上で実行されるときに ISR が保存する操作固有のデータを、ISR によって上書きされないように保護できると想定しないでください。
ドライバーは、ISR によって収集されたデータを保持するために、すべての割り込み駆動型 I/O 操作をシリアル化しようと試みる可能性がありますが、そのドライバーは、SMP コンピューターでは、ユニプロセッサ コンピューターよりもはるかに高速に実行されるわけではありません。 ユニプロセッサ プラットフォームとマルチプロセッサ プラットフォーム間で移植性を保ちながら、ドライバーのパフォーマンスを最大限に高めるには、他の手法を使用して、DpcForIsr による後続の処理のために ISR によって取得された操作固有のデータを保存する必要があります。
たとえば、ISR は、DpcForIsr に渡す IRP に操作固有のデータを保存できます。 この手法を改良するには、ISR 拡張カウントを参照し、ISR が提供するデータを使用して数の IRP の数を処理し、返される前にカウントを 0 にリセットする DpcForIsr を実装します。 もちろん、ISR と SynchCritSection では値が動的に保持されるため、カウントはドライバーの割り込みスピン ロックによって保護されなければなりません。