为什么要有互斥体:等待对象被遗弃
互斥体(MUTANT)与事件(EVENT)和信号量(SEMAPHORE)一样,都可以用来·进行线程的同步控制。
但需要指出的是,这几个对象都是内核对象,这就意味着,通过这些对象可以进行跨进程的线程同步控制,比如:
A进程中的X线程与B进程中的Y线程,它们可以共同使用一个内核对象来进行线程同步控制。
极端情况:
如果B进程的Y线程还没有来得及调用修改SignalState的函数(如SetEvent)那么等待对象Z将被遗弃,这也就以为者x线程将永远等下去!
但如果我们用的是互斥体这种情况就很好解决,即使你的B进程的Y线程还没有来得及调用修改SignalState的函数,系统也会帮你修改。
因为创建了互斥体时,已经将互斥体已经将挂到了互斥体链表里,系统的函数可以通过binitialOwner查找到你当前线程所占用的互斥体。
当前线程并不维护事件与信号量,但是它维护互斥体对象
互斥体允许重入
上面调用了WaitForSingleObject等待对象是A,由于逻辑方面的要求后面有调用了WaitForMultipleObjects等待对象为A,B,C。
调用WaitForSingleObject(A)的时候已经将A对象的SignalState改为0了没有信号了,那就必须在你的WaitForSingleObject(A)执行完毕后调用SetEvent来修改SignalState为1。
这时就出问题了由于图上的A这时是没有信号的,这时你的代码将永远停留在WaitForMultipleObjects(A,B,C)这个位置,这种情况就叫死锁。
解决方案:
如果你需要多次重复进入临界区的话,这时你的A对象是互斥体就不会出现死锁。
互斥体与事件/信号量的第二个区别就是它允许重复进入临界区。
MUTANT结构体介绍
kd> dt _KMUTANT
nt!_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY
+0x018 OwnerThread : Ptr32 _KTHREAD //当前互斥体对象所属线程
+0x01c Abandoned : UChar
+0x01d ApcDisable : UChar
+0x010 MutantListEntry
拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头**圈着所有互斥体**
+0x018 OwnerThread:
正在拥有互斥体的线程
+0x01c Abandoned:
是否已经被放弃不用
+0x01d ApcDisable:
是否禁用内核APC 0不禁用 1禁用
(3环)Mutant 对应内核函数 NtCreateMutant ApcDisable = 0
(0环)Mutex 对应内核函数 NtCreateMutex ApcDisable = 1
(参见 KeWaitForsingleObject 函数)
CreateMutex创建互斥体
HANDLE CreateMutex
(
LPSECURITYATTRIBUTE SlpMutexAttributes, //指向安全属性的指针
BOOL bnitialOwner, //初始化互斥对象的拥有者
LPCTSTR IpName //指向互斥对象名的指针
)
参数2:TRUE代表就是当前线程的,
FALSE代表这个互斥体不是当前线程只是将他创建出来
CreateMutex -> NtCreateMutant(内核函数) -> KelnitializeMutant(内核函数)
初始化MUTANT结构体:
MUTANT.Header.Type=2;
MUTANT.Header.SianalState =binitialOwner ?0:1;//参数2初始值为true它就为0
MUTANT.OwnerThread=当前线程 or NULL;//参数2初始值为true当前线程,false就是0
MUTANT.Abandoned = 0;
MUTANT.ApcDisable=0;
如果 blnitialOwner == TRUE 将当前互斥体挂入到当前线程的互斥体链表(KTHREAD+0x010 MutantListHead)
ReleaseMutex释放临界区
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
ReleaseMutex ->NtReleaseMutant ->KeReleaseMutant
正常调用时:
MUTANT.Header.SignalState++;
如果SignalState=1(有信号了) 说明其他进程可用了 将该互斥体从线程链表中移除。
互斥体解决等待对象被遗弃问题
_KMUTANT
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListEntry : _LIST_ENTRY
+0x018 OwnerThread : Ptr32 _KTHREAD //当前互斥体对象所属线程
+0x01c Abandoned : UChar
+0x01d ApcDisable : UChar
+0x010 MutantListEntry:
拥有互斥体线程(KTHREAD+0x010 MutantListHead)是个链表头圈着所有互斥体
+0x01c Abandoned:
是否已经被放弃不用
MmUnloadSystemlmage -> KeReleaseMutant(X, Y,Abandon,Z)//是否被丢弃
KeReleaseMutant的第3个参数可以知道是被正常释放,还是被丢弃的
正常是FALSE
丢弃是TRUE,系统会直接将SignalState置为有信号状态
if(Abandon == false) //正常调用
{
MUTANT.Header.SignalState++;
}else//丢弃
{
MUTANT.Header.SignalState == 1;
MUTANT.OwnerThread == NULL;
}
if(MUTANT.Header.SignalState==1)
{
MUTANT.OwnerThread == NULL;
//从当前线程互斥体链表中将当前互斥体移除
}
进入互斥体