显示关键节

关键部分可以通过各种不同的方法在用户模式下显示。 每个字段的确切含义取决于所使用的 Microsoft Windows 版本。

显示关键部分

关键部分可由 !ntsdexts.locks 扩展、 !critsec 扩展、 !cs 扩展和 dt (Display Type) 命令显示。

!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 (Display Type) 命令可用于显示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 开始。 每次调用 EnterCriticalSection 都会递增此值;每次调用 LeaveCriticalSection 都会递减它。 例如,如果 LockCount 为 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

调试器将“NOT LOCKED”显示为 LockCount 的值。 此字段对于已解锁的关键部分的实际值为 -1。 可以使用 dt (Display Type) 命令对此进行验证:

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 字段全部递增一个, 而 OwningThread 将成为调用方线程 ID。 EntryCountContentionCount 永远不会递减。 例如:

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

此时,可能会发生四个不同的事情。

  1. 拥有线程再次调用 EnterCriticalSection 。 这会递增 LockCountRecursionCountEntryCount 不递增。

    0:000> !critsec 433e60
    CritSec mymodule!cs+0 at 00433E60
    LockCount          1
    RecursionCount     2
    OwningThread       4d0
    EntryCount         0
    ContentionCount    0
    
  2. 另一个线程调用 EnterCriticalSection。 这会递增 LockCountEntryCountRecursionCount 不递增。

    0:000> !critsec 433e60
    CritSec mymodule!cs+0 at 00433E60
    LockCount          1
    RecursionCount     1
    OwningThread       4d0
    EntryCount         1
    ContentionCount    1
    
  3. 拥有的线程调用 LeaveCriticalSection。 这会将 LockCount (减为 -1) , 将 RecursionCount (减为 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 的线程相同的结果-它将 LockCount (减为 -1) ,recursionCount (减为 0) ,并将 OwningThread 重置为 0。

当任何线程调用 LeaveCriticalSection 时,Windows 会递减 LockCountRecursionCount。 此功能具有好方面和坏方面。 它允许设备驱动程序在一个线程上进入关键部分,并将关键部分保留在另一个线程上。 但是,它也有可能在错误的线程上意外调用 LeaveCriticalSection ,或者调用 LeaveCriticalSection 太多次,导致 LockCount 达到低于 -1 的值。 这会损坏关键部分,并导致所有线程无限期地等待关键部分。

解释 Windows Server 2003 SP1 及更高版本中的关键节字段

在 Microsoft Windows Server 2003 Service Pack 1 及更高版本的 Windows 中, LockCount 字段分析如下:

  • 最低位显示锁定状态。 如果此位为 0,则锁定关键部分;如果为 1,则不锁定关键部分。

  • 下一位显示是否已为此锁唤醒线程。 如果此位为 0,则表示已为此锁唤醒线程;如果为 1,则表示尚未唤醒任何线程。

  • 剩余的位是等待锁的线程数的补码。

例如,假设 LockCount 为 -22。 可按以下方式确定最低位:

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

可按以下方式确定下一个最低位:

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

可以按以下方式确定剩余位的补数:

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

在此示例中,第一个位为 0,因此关键部分被锁定。 第二个位为 1,因此尚未为此锁唤醒任何线程。 剩余位的补数为 5,因此有 5 个线程等待此锁。

其他信息

有关如何调试关键节超时的信息,请参阅 关键节超时。 有关关键部分的一般信息,请参阅 Microsoft Windows SDK、Windows 驱动程序工具包 (WDK) 或 Microsoft Windows Internals(由 Mark Russinovich 和 David Solomon 撰写)。