临界区死锁和死循环

1.临界区

使用CRITICAL_SECTION 结构来保证不会有多个线程重入被保护的代码段

实现在用户态的同步机制,相对内核对象来说,开销更小

适用于同步同一进程内的多个线程

如:

CRITICAL_SECTION g_cs;

EnterCriticalSection(&g_cs);

//以原子方式访问共享资源

LeaveCriticalSection(&g_cs);

RTL_CRITICAL_SECTION 结构如下:

struct RTL_CRITICAL_SECTION

{

    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

    LONG LockCount;

    LONG RecursionCount;

    HANDLE OwningThread;

    HANDLE LockSemaphore;

    ULONG_PTR SpinCount;

};

LockCount字段,初始值为-1,被线程拥有后,大于等于0,反映等待和已经进入关键区的线程数

RecursionCount字段,此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功

OwningThread字段,拥有线程(已经进入临界区)的句柄

LockSemaphore字段,唤醒等待的线程在初始化临界区结构时,系统会分配一个RTL_CRITICAL_SECTION_DEBUG结构,每个临界区的调试结构依靠ProcessLocksList字段相互联系在一起

SpinCount 仅用于多处理器系统。MSDN文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount 次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用 InitializeCriticalSectionAndSpinCount API 将其设置为一个不同值。

临界区的调试支持,RTL_CRITICAL_SECTION_DEBUG结构如下:

struct _RTL_CRITICAL_SECTION_DEBUG

{

    WORD   Type;

    WORD   CreatorBackTraceIndex;

    RTL_CRITICAL_SECTION *CriticalSection;

    LIST_ENTRY ProcessLocksList;

    DWORD EntryCount;

    DWORD ContentionCount;

    DWORD Spare[ 2 ];

}

这一结构由 InitializeCriticalSection 分配和初始化。它既可以由 NTDLL

内的预分配数组分配,也可以由进程堆分配。RTL_CRITICAL_SECTION

的这一伴随结构包含一组匹配字段,具有迥然不同的角色:有两个难以理解,随后两个提供了理解这一临界区链结构的关键,两个是重复设置的,最后两个未使用。

下面是对 RTL_CRITICAL_SECTION 字段的说明。

Type 此字段未使用,被初始化为数值 0。

CreatorBackTraceIndex 此字段仅用于诊断情形中。在注册表项

HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution

Options\YourProgram 之下是 keyfield、GlobalFlag 和 StackTraceDatabaseSizeInMb

值。注意,只有在运行稍后说明的 Gflags 命令时才会显示这些值。这些注册表值的设置正确时,CreatorBackTraceIndex

字段将由堆栈跟踪中所用的一个索引值填充。在 MSDN 中搜索 GFlags 文档中的短语“create user mode stack trace

database”和“enlarging the user-mode stack trace database”,可以找到有关这一内容的更多信息。

CriticalSection 指向与此结构相关的 RTL_CRITICAL_SECTION。图

1 说明该基础结构以及 RTL_CRITICAL_SECTION、RTL_CRITICAL_SECTION_DEBUG

和事件链中其他参与者之间的关系。

 

图 1 临界区的关系

ProcessLocksList LIST_ENTRY 是用于表示双向链表中节点的标准 Windows数据结构。RTL_CRITICAL_SECTION_DEBUG 包含了链表的一部分,允许向前和向后遍历该临界区。

EntryCount/ContentionCount

这些字段在相同的时间、出于相同的原因被递增。这是那些因为不能马上获得临界区而进入等待状态的线程的数目。与 LockCount 和 RecursionCount字段不同,这些字段永远都不会递减。

Spares这两个字段未使用,甚至未被初始化(尽管在删除临界区结构时将这些字段进行了清零)。

即使 RTL_CRITICAL_SECTION_DEBUG中包含多个字段,它也是常规临界区结构的必要成分。事实上,如果系统恰巧不能由进程堆中获得这一结构的存储区,InitializeCriticalSection

将返回为 STATUS_NO_MEMORY 的 LastError 结果,然后返回处于不完整状态的临界区结构。

2. 经典死锁案例:

启动windbg 附加MulThrds.exe

·1.~*  查看所有线程

 

2.~* k 查看所有线程堆栈

发现有几个线程都在等待进入同一个临界区

3.切换其中某一个线程,查看堆栈,如切到换10号线程

4.查看临界区 !cs 0040b32c

5.查看死锁的临界区,刚好也是0040b32c

拥有的线程是0x00002e14,查看所有线程,发现0x00002e14 已经不在了。

3.查找死循环案例

启动windbg 附加MulThrds.exe

1.查看线程运行时间

~*e .ttime;.echo*****

 2.输出线程tid

~*e .echo **********;? @$tid; .ttime;

 3.切换线程

~~[4040]s

 查看堆栈信息:

 

参考 : 软件调试  ----张银奎

MulThrds.exe 及源码请查看软件调试


版权声明:本文为maomao171314原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。