クリティカル セクションの表示

クリティカル セクションは、さまざまな方法を使用してユーザー モードで表示できます。 各フィールドの正確な意味は、使用している Microsoft Windows バージョンによって異なります。

クリティカル セクションの表示

クリティカル セクションは、!ntsdexts.locks 拡張機能、!critsec 拡張機能、!cs 拡張機能、dt (タイプの表示) コマンドを使用して表示できます。

!ntsdexts.locks 拡張機能では、現在のプロセスに関連付けられているクリティカル セクションの一覧が表示されます。 -v オプションを使用すると、すべてのクリティカル セクションが表示されます。 例を次に示します。

0:000> !locks

CritSec ntdll!FastPebLock+0 at 77FC49E0
LockCount          0
RecursionCount     1
OwningThread       c78
EntryCount         0
ContentionCount    0
*** Locked

....
Scanned 37 critical sections

表示するクリティカル セクションのアドレスがわかっている場合は、!critsec 拡張機能を使用できます。 これにより、!ntsdexts.locks と同じ情報のコレクションが表示されます。 次に例を示します。

0:000> !critsec 77fc49e0

CritSec ntdll!FastPebLock+0 at 77FC49E0
LockCount          0
RecursionCount     1
OwningThread       c78
EntryCount         0
ContentionCount    0
*** Locked

!cs 拡張機能では、そのアドレスに基づいてクリティカル セクションを表示したり、クリティカル セクションのアドレス範囲を検索したりできます。また、各クリティカル セクションに関連付けられているスタック トレースを表示することもできます。 これらの機能の一部は、完全な Windows シンボルが正常に動作することを必要とします。 アプリケーション検証ツールがアクティブな場合は、!cs -t を使用してクリティカル セクション ツリーを表示できます。 詳細と例については、!cs のリファレンス ページを参照してください。

!cs によって表示される情報は、!ntsdexts.locks および !critsec によって表示される情報とは少し異なります。 次に例を示します。

## 0:000> !cs 77fc49e0

Critical section   = 0x77fc49e0 (ntdll!FastPebLock+0x0)
DebugInfo          = 0x77fc3e00
LOCKED
LockCount          = 0x0
OwningThread       = 0x00000c78
RecursionCount     = 0x1
LockSemaphore      = 0x0
SpinCount          = 0x00000000

dt (タイプの表示) コマンドを使用すると、RTL_CRITICAL_SECTION 構造体のリテラル コンテンツを表示できます。 次に例を示します。

0:000> dt RTL_CRITICAL_SECTION 77fc49e0
   +0x000 DebugInfo        : 0x77fc3e00 
   +0x004 LockCount        : 0
   +0x008 RecursionCount   : 1
   +0x00c OwningThread     : 0x00000c78 
   +0x010 LockSemaphore    : (null) 
   +0x014 SpinCount        : 0

Windows XP および Windows 2000 でのクリティカル セクション フィールドの解釈

クリティカル セクション構造の最も重要なフィールドは次のとおりです。

  • Microsoft Windows 2000 および Windows XP では、LockCount フィールドは、スレッドがこのクリティカル セクションの EnterCriticalSection ルーチンを呼び出した回数から 1 を引いた数を示します。 ロック解除されたクリティカル セクションの場合、このフィールドは -1 から始まります。 EnterCriticalSection を呼び出すたびに、この値が 1 だけ増えます。LeaveCriticalSection を呼び出すたびに、この値が 1 だけ減ります。 たとえば、LockCount が 5 の場合、このクリティカル セクションはロックされており、1 つのスレッドがそれを取得し、さらに 5 つのスレッドがこのロックを待機しています。

  • RecursionCount フィールドは、所有しているスレッドがこのクリティカル セクションに対して EnterCriticalSection を呼び出した回数を示します。

  • EntryCount フィールドは、所有しているスレッド以外のスレッドがこのクリティカル セクションに対して EnterCriticalSection を呼び出した回数を示します。

新しく初期化されたクリティカル セクションは次のようになります。

0:000> !critsec 433e60
CritSec mymodule!cs+0 at 00433E60
LockCount          NOT LOCKED 
RecursionCount     0
OwningThread       0
EntryCount         0
ContentionCount    0

デバッガーには、LockCount の値として "NOT LOCKED" が表示されます。 ロック解除されたクリティカル セクションの場合、このフィールドの実際の値は -1 です。 これは、dt (タイプの表示) コマンドを使用して確認できます。

0:000> dt RTL_CRITICAL_SECTION 433e60
   +0x000 DebugInfo        : 0x77fcec80
   +0x004 LockCount        : -1
   +0x008 RecursionCount   : 0
   +0x00c OwningThread     : (null) 
   +0x010 LockSemaphore    : (null) 
   +0x014 SpinCount        : 0

最初のスレッドが EnterCriticalSection ルーチンを呼び出すと、クリティカル セクションの LockCountRecursionCountEntryCountContentionCount の各フィールドがすべて 1 ずつ増やされ、OwningThread が呼び出し元のスレッド ID になります。 EntryCountContentionCount が減らされることはありません。 次に例を示します。

0:000> !critsec 433e60
CritSec mymodule!cs+0 at 00433E60
LockCount          0
RecursionCount     1
OwningThread       4d0
EntryCount         0
ContentionCount    0

この時点で、4 つの異なる処理が行われる可能性があります。

  1. 所有しているスレッドが EnterCriticalSection をもう一度呼び出します。 これにより、LockCountRecursionCount が 1 だけ増やされます。 EntryCount は増やされません。

    0:000> !critsec 433e60
    CritSec mymodule!cs+0 at 00433E60
    LockCount          1
    RecursionCount     2
    OwningThread       4d0
    EntryCount         0
    ContentionCount    0
    
  2. 別のスレッドが EnterCriticalSection を呼び出します。 これにより、LockCountEntryCount が 1 だけ増やされます。 RecursionCount は増やされません。

    0:000> !critsec 433e60
    CritSec mymodule!cs+0 at 00433E60
    LockCount          1
    RecursionCount     1
    OwningThread       4d0
    EntryCount         1
    ContentionCount    1
    
  3. 所有しているスレッドが LeaveCriticalSection を呼び出します。 これにより、LockCountRecursionCount が 1 ずつ減らされて、それぞれ -1 と 0 になり、OwningThread が 0 にリセットされます。

    0:000> !critsec 433e60
    CritSec mymodule!cs+0 at 00433E60
    LockCount          NOT LOCKED 
    RecursionCount     0
    OwningThread       0
    EntryCount         0
    ContentionCount    0
    
  4. 別のスレッドが LeaveCriticalSection を呼び出します。 これにより、LeaveCriticalSection を呼び出す所有スレッドと同じ結果が生成されます。LockCountRecursionCount が 1 ずつ減らされて、それぞれ -1 と 0 になり、OwningThread が 0 にリセットされます。

スレッドが LeaveCriticalSection を呼び出すと、Windows は LockCountRecursionCount を 1 ずつ減らします。 この機能には、良い面と悪い面の両方があります。 デバイス ドライバーは、1 つのスレッドでクリティカル セクションに入り、別のスレッドでクリティカル セクションを離れることができます。 ただし、誤って間違ったスレッドで LeaveCriticalSection を呼び出したり、LeaveCriticalSection を何度も呼び出したために LockCount が -1 より小さい値になったりする可能性もあります。 これにより、クリティカル セクションが破損し、すべてのスレッドがクリティカル セクションで無期限に待機する原因となります。

Windows Server 2003 SP1 以降でのクリティカル セクション フィールドの解釈

Microsoft Windows Server 2003 Service Pack 1 以降のバージョンの Windows では、LockCount フィールドは次のように解析されます。

  • 最下位ビットはロック状態を示します。 このビットが 0 の場合、クリティカル セクションはロックされています。1 の場合、クリティカル セクションはロックされていません。

  • 次のビットは、スレッドがこのロックに対して開始されたかどうかを示します。 このビットが 0 の場合、このロックに対してスレッドが開始されています。1 の場合、スレッドは開始されていません。

  • 残りのビットは、ロックを待機しているスレッド数の 1 の補数です。

たとえば、LockCount が -22 であるとします。 最下位ビットは次のように決定できます。

0:009> ? 0x1 & (-0n22)
Evaluate expression: 0 = 00000000

次に下位のビットは、次のように決定できます。

0:009> ? (0x2 & (-0n22)) >> 1
Evaluate expression: 1 = 00000001

残りのビットの 1 の補数は、次のように決定できます。

0:009> ? ((-1) - (-0n22)) >> 2
Evaluate expression: 5 = 00000005

この例では、最初のビットが 0 であるため、クリティカル セクションはロックされています。 2 番目のビットは 1 であるため、このロックに対してスレッドは開始されていません。 残りのビットの補数は 5 であるため、このロックを待機しているスレッドは 5 つあります。

追加情報

クリティカル セクションのタイムアウトをデバッグする方法については、「クリティカル セクションのタイムアウト」を参照してください。 クリティカル セクションの全般情報については、Microsoft Windows SDK のドキュメント、Windows Driver Kit (WDK) のドキュメント、または Mark Russinovich および David Solomon による Microsoft Windows Internals を参照してください。