★.可等待对象一览
1. Kevent 就是一个等待头 没啥说的 以上的例子也是举得这个KMUTANT 对应的是r3的mutex
typedef struct _KMUTANT {
DISPATCHER_HEADER Header;
LIST_ENTRY MutantListEntry; KTHREAD.MutantListHead
struct _KTHREAD *OwnerThread; //一个互斥体属于某个线程
BOOLEAN Abandoned; //是否已经弃用
UCHAR ApcDisable; // 是否禁用某种apc 会影响到线程的响应
} KMUTANT, *PKMUTANT, *PRKMUTANT, KMUTEX, *PKMUTEX, *PRKMUTEX;
只有所属线程才能释放这个对象,所以这个对象可以算是特别为线程访问资源同步设计的
2. 信号量
只是在DISPATCHER_header 后面加上一个Limit 数 限制了共享资源的最大值~ 差不对
3. 队列
KQUEUE 用来实现线程池 ,也就是实现的一组线程 限制了最大的活动线程数,超出最大线程数之后,线程只能进入等待,等到其他线程不活动了之后自己再活动
typedef struct _KQUEUE {
DISPATCHER_HEADER Header;
LIST_ENTRY EntryListHead; 队列中有待处理的项
ULONG CurrentCount; 活动线程数
ULONG MaximumCount;
LIST_ENTRY ThreadListHead; 节点是KTHREAD.QueueListHead
} KQUEUE, *PKQUEUE, *PRKQUEUE;
插入一个队列时,如果活动线程数小于最大数,并且有线程在等待队列 立即释放,否则就被插入到EntryListHead
4. 进程线程 挺简单不多说
5. 定时器
最后的tips
1.等待的线程解除时,优先级上升1(定时器除外),并且时限-1(不同系统环境未必一样)
2.在处理分发对象头时都要提升IRQL并且锁住分发数据库,这表明进入了一段线程调度器的代码 线程调度分布在 中断 异常 以及各种处理线程相关代码逻辑的位置
一般情况下都是在处理过之后退出,单象 KeSetEvent 有一个Wait变量,如果指明为true 证明可以保证有线程在等待 这时不退出调度器,而是
if (Wait != FALSE) {
Thread = KeGetCurrentThread();
Thread->WaitNext = Wait;
Thread->WaitIrql = OldIrql;
} else {
KiUnlockDispatcherDatabase(OldIrql);
}
然后由等待的线程来处理,不知道这样做的优化到底有多少,但我知道我当年由于不好好看文档,用错这个参数,反正是没少蓝屏。。。。。。
★.门等待
绕过繁琐的线程调度,实现快速等待。实际上就是把门对象从线程等待对象的机制中抽离出来,自己实现一套专门的等待机制,
对象是一个KGATE 就是分发器头部对象,只不过开头被解释为一个锁
VOID
FASTCALL
KeWaitForGate (
__inout PKGATE Gate,
__in KWAIT_REASON WaitReason,
__in KPROCESSOR_MODE WaitMode
)
{
PKTHREAD CurrentThread;
KLOCK_QUEUE_HANDLE LockHandle;
PKQUEUE Queue;
PKWAIT_BLOCK WaitBlock;
NTSTATUS WaitStatus;
CurrentThread = KeGetCurrentThread();
do {
KeAcquireInStackQueuedSpinLockRaiseToSynch(&CurrentThread->ApcQueueLock,
&LockHandle);
//先交付一下apc看看
if (CurrentThread->ApcState.KernelApcPending &&
(CurrentThread->SpecialApcDisable == 0) &&
(LockHandle.OldIrql < APC_LEVEL)) {
KeReleaseInStackQueuedSpinLock(&LockHandle);
continue;
}
//有队列才去锁?
if ((Queue = CurrentThread->Queue) != NULL) {
KiLockDispatcherDatabaseAtSynchLevel();
}
KiAcquireThreadLock(CurrentThread);//Thread->ThreadLock
KiAcquireKobjectLock(Gate); //对象分发头的锁
if (Gate->Header.SignalState != 0) { //有信号就立即满足
Gate->Header.SignalState = 0; //立即重置
KiReleaseKobjectLock(Gate);
KiReleaseThreadLock(CurrentThread);
if (Queue != NULL) {
KiUnlockDispatcherDatabaseFromSynchLevel();
}
KeReleaseInStackQueuedSpinLock(&LockHandle);
break;
} else {
WaitBlock = &CurrentThread->WaitBlock[0];//构造等待块
WaitBlock->Object = Gate;
WaitBlock->Thread = CurrentThread;
CurrentThread->WaitMode = WaitMode;
CurrentThread->WaitReason = WaitReason;
CurrentThread->WaitIrql = LockHandle.OldIrql;
CurrentThread->State = GateWait; //传说中的门等待状态~~现身~~ 不再放入等待对象中
CurrentThread->GateObject = Gate;
InsertTailList(&Gate->Header.WaitListHead, &WaitBlock->WaitListEntry); //这边的链表还是要弄好
KiReleaseKobjectLock(Gate);
KiSetContextSwapBusy(CurrentThread);
KiReleaseThreadLock(CurrentThread);
//
// If the current thread is associated with a queue object, then
// activate another thread if possible.
//
if (Queue != NULL) { //线程池,如果有 把执行权让给其他线程
if ((Queue = CurrentThread->Queue) != NULL) {
KiActivateWaiterQueue(Queue);
}
KiUnlockDispatcherDatabaseFromSynchLevel();
}
KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);
WaitStatus = (NTSTATUS)KiSwapThread(CurrentThread, KeGetCurrentPrcb()); //线程切换
if (WaitStatus == STATUS_SUCCESS) {
return;
}
}
} while (TRUE);
return;
}
KeSignalGateBoostPriority这个函数获取到等待门对象的线程,直接放入延迟准备中去
Entry = Gate->Header.WaitListHead.Flink;
WaitBlock = CONTAINING_RECORD(Entry, KWAIT_BLOCK, WaitListEntry);
WaitThread = WaitBlock->Thread; //取得等待的线程
if (KiTryToAcquireThreadLock(WaitThread)) {
RemoveEntryList(Entry);
WaitThread->WaitStatus = STATUS_SUCCESS;
WaitThread->State = DeferredReady; //设置成延迟准备 直接调用
WaitThread->DeferredProcessor = KeGetCurrentPrcb()->Number;
KiReleaseKobjectLock(Gate);
KiReleaseThreadLock(WaitThread);
Priority = CurrentThread->Priority;
if (Priority < LOW_REALTIME_PRIORITY) { //调整优先级!
Priority = Priority - CurrentThread->PriorityDecrement;
if (Priority < CurrentThread->BasePriority) {
Priority = CurrentThread->BasePriority;
}
if (CurrentThread->PriorityDecrement != 0) {
CurrentThread->PriorityDecrement = 0;
CurrentThread->Quantum = CLOCK_QUANTUM_DECREMENT;
}
}
WaitThread->AdjustIncrement = Priority;
WaitThread->AdjustReason = (UCHAR)AdjustBoost;
if (WaitThread->Queue != NULL) {
KiLockDispatcherDatabaseAtSynchLevel();
if ((Queue = WaitThread->Queue) != NULL) {
Queue->CurrentCount += 1;
}
KiUnlockDispatcherDatabaseFromSynchLevel();
}
KiDeferredReadyThread(WaitThread);
KiExitDispatcher(OldIrql);
return;
} else {//继续尝试获取
KiReleaseKobjectLock(Gate);
KeLowerIrql(OldIrql);
continue;
}
守护互斥体KGUARD_MUTEX是用互斥实现的 还有快速互斥 x86基于事件 x64基于门等待
★.两种执行体层面的同步机制——执行体互斥&push lock
两种同步机制基于上述的一些基本同步机制而实现,提供独占资源和共享资源的读取能力,这里只简单的看一下逻辑。
先看执行体资源的结构定义
typedef struct _ERESOURCE {
LIST_ENTRY SystemResourcesList; 执行体资源由系统的一个链表ExpSytemResourceList统一管理
POWNER_ENTRY OwnerTable; 动态数组 保存共享属性和等待这个资源的线程的一些信息
SHORT ActiveCount; 当前获得这个资源的线程总数
USHORT Flag; 状态标志位
PKSEMAPHORE SharedWaiters; 信号量对象,利用信号量实现共享资源计数的统计
PKEVENT ExclusiveWaiters; 事件对象,用来实现独占
OWNER_ENTRY OwnerThreads[2]; 自己本身有两个owner_entry 提升效率
ULONG ContentionCount; 发生竞争的次数
USHORT NumberOfSharedWaiters; 共享等待数
USHORT NumberOfExclusiveWaiters; 独占等待数
union {
PVOID Address;
ULONG_PTR CreatorBackTraceIndex;
};
KSPIN_LOCK SpinLock; 保证处理这个结构时的同步
} ERESOURCE, *PERESOURCE;
typedef struct _OWNER_ENTRY {
ERESOURCE_THREAD OwnerThread; 所有者线程
union {
LONG OwnerCount; 引用计数
ULONG TableSize;
};
} OWNER_ENTRY, *POWNER_ENTRY;
这个对象在非换页池分配,读访问时可以共享读,写的时候独占写,这是一种优化策略
以独占方式释放后,共享请求和独占请求都被发出,先满足共享在满足独占
共享被释放后,只接受独占请求
1. 请求独占访问时,
如果ActiveCount为0,证明没有人在访问这个资源 给予分配资源,否则就进入等待ExclusiveWaiters,
如果等待超时,试图提升正在独占或者所有正在共享这个资源的优先级,以便使这些线程能够尽快释放这个资源
2. 请求共享访问
如果ActiveCount为0,则填充一个OwnerThreads返回
如果当前线程已经独占该资源,增加第一个owner_entry的引用计数
如果该资源已经共享,就查看当前线程是否在OwnerThreads 如果不在且没有其他线程在等待独占,则加入;如果在数组中只增加相应的引用计数
以下是需要等待或者失败的情况(这取决于调用者悬着是否等待 wait参数):
该资源已经被其他线程独占
该资源已经共享,当前线程不再owner的数组中,并且现在有一个正在请求独占的线程
这两种情况,都要把自己添加到OwnerTread中,然后等待信号量的释放
3. 释放资源
如果是独占方式访问,减少引用计数,如果已经是0,清除owner thread,检查是否有共享请求如果有就释放信号量,如果没有共享请求就看是否有独占请求、有就设置设置事件
如果之前是共享方式访问 处理相应的owner thread 如果引用计数是0 则清除掉owner thread。如果ActiveCount为0,就检查是否有独占访问请求,设置事件满足等待者。
下面看一下push lock
typedef struct _EX_PUSH_LOCK {
union {
struct {
ULONG_PTR Locked : 1; 表示已经被锁
ULONG_PTR Waiting : 1; 表示有先线程正在等待
ULONG_PTR Waking : 1; 一个线程试图唤醒另外一个线程
ULONG_PTR MultipleShared : 1; 共享线程数
ULONG_PTR Shared : sizeof (ULONG_PTR) * 8 - 4; w位是0, 后28是共享计数, 否则后28指向一个等待块
};
ULONG_PTR Value;
PVOID Ptr;
};
} EX_PUSH_LOCK, *PEX_PUSH_LOCK;
后28指向一个等待块
typedef struct DECLSPEC_ALIGN(16) _EX_PUSH_LOCK_WAIT_BLOCK {
union {
KGATE WakeGate;
KEVENT WakeEvent;
};
PEX_PUSH_LOCK_WAIT_BLOCK Next;
PEX_PUSH_LOCK_WAIT_BLOCK Last;
PEX_PUSH_LOCK_WAIT_BLOCK Previous;
LONG ShareCount;
LONG Flags;
} DECLSPEC_ALIGN(16) EX_PUSH_LOCK_WAIT_BLOCK;
Push lock在无竞争情况下(w始终为0),可以有一下情况
独占获取 前提:所有位全是0 获取之后成功: L位变为1
共享获取 前提:L 1 shared存在 或者所有位是0,获取之后成功:L为1 shared++
独占释放 L变回0
共享释放 shared--,如果没有块之后 L变为0
在有竞争情况下:
独占获取 前提:L=1 W=0 动作:构造一等待块 放入shared 然后置w位 1
前提:L=1 W=1 动作:构造一等待块 放入shared
然后等待门对象
共享获取
前提:L=1 shared=0 意味着这是一个独占的 动作:构造等待块 w=1
前提 L=1 W= 1 shared有值 意味独占中 并且有等待共享的线程 动作:构造一个等待块放入
独占释放
尝试找到一个独占式请求,释放门对象唤醒线程,如果没有,再唤醒共享的线程
共享释放
释放请求独占的线程
实际在win的实现中 还要处理K m 两位。