📄 漫谈兼容内核之十五:windows线程的等待、唤醒机制.txt
字号:
/* Lock is held, disable Wait Next */
DPRINT("Lock is held\n");
CurrentThread->WaitNext = FALSE;
} else {
/* Lock not held, acquire it */
DPRINT("Lock is not held, acquiring\n");
CurrentThread->WaitIrql = KeAcquireDispatcherDatabaseLock();
}
/* Start the actual Loop */
do {
/* Get the current Wait Status */
WaitStatus = CurrentThread->WaitStatus;
/* Append wait block to the KTHREAD wait block list */
CurrentThread->WaitBlockList = WaitBlock = &CurrentThread->WaitBlock[0];
/* Get the Current Object */
CurrentObject = (PDISPATCHER_HEADER)Object;
/* FIXME:
* Temporary hack until my Object Manager re-write.. . . . . .
*/
if (CurrentObject->Type == IO_TYPE_FILE) {
. . . . . .
}
/* Check if the Object is Signaled */
if (KiIsObjectSignaled(CurrentObject, CurrentThread)) {
/* Just unwait this guy and exit */
if (CurrentObject->SignalState != MINLONG) {
/* It has a normal signal state, so unwait it and return */
KiSatisfyObjectWait(CurrentObject, CurrentThread);
Status = STATUS_WAIT_0;
goto WaitDone;
} else {
/* Is this a Mutant? */
if (CurrentObject->Type == MutantObject) {
/* According to wasm.ru, we must raise this exception (tested and true) */
KeReleaseDispatcherDatabaseLock(CurrentThread->WaitIrql);
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
}
}
}
/* Set up the Wait Block */
WaitBlock->Object = CurrentObject;
WaitBlock->Thread = CurrentThread;
WaitBlock->WaitKey = (USHORT)(STATUS_WAIT_0);
WaitBlock->WaitType = WaitAny;
WaitBlock->NextWaitBlock = NULL;
/* Make sure we can satisfy the Alertable request */
KiCheckAlertability(Alertable, CurrentThread, WaitMode, &Status);
/* Set the Wait Status */
CurrentThread->WaitStatus = Status;
/* Enable the Timeout Timer if there was any specified */
if (Timeout != NULL) {
. . . . . .
}
/* Link the Object to this Wait Block */
InsertTailList(&CurrentObject->WaitListHead, &WaitBlock->WaitListEntry);
/* Handle Kernel Queues */
if (CurrentThread->Queue) {
DPRINT("Waking Queue\n");
KiWakeQueue(CurrentThread->Queue);
}
/* Block the Thread */
. . . . . .
PsBlockThread(&Status, Alertable, WaitMode, (UCHAR)WaitReason);
/* Check if we were executing an APC */
if (Status != STATUS_KERNEL_APC) {
/* Return Status */
return Status;
}
DPRINT("Looping Again\n");
CurrentThread->WaitIrql = KeAcquireDispatcherDatabaseLock();
} while (TRUE);
WaitDone:
/* Release the Lock, we are done */
. . . . . .
KeReleaseDispatcherDatabaseLock(CurrentThread->WaitIrql);
return Status;
}[/code]
为简单起见,我们略去了代码中对于超时(Timeout)的设置,也略去了原作者所加的一大段注释,说日后还要对代码进行改写云云。此外,还略去了当目标对象的类型为IO_TYPE_FILE时的处理,因为我们现在还没有深入到设备驱动中。
代码中首先检查CurrentThread->WaitNext是否为0,以决定在本次调用中是否需要通过KeAcquireDispatcherDatabaseLock()来“锁定”线程调度,即禁止因别的原因(例如中断)而引起的线程调度。注意这里锁定线程调度是必要的,CurrentThread->WaitNext为TRUE只是说明原来就已经锁定,不应该再来锁定了。有关线程调度及其锁定以后在别的漫谈中还会讲到。
然后就是程序的主体了。这是一个do{}while(TRUE)无限循环,这循环一共有3个出口,其中之一与Timeout有关,由于有关的代码已被略去,在这里已经看不见了。剩下的两个出口,一个是条件语句“if (KiIsObjectSignaled(CurrentObject, CurrentThread))”里面的“goto WaitDone”,这说明目标对象已经收到“信号”、或者说此前已经“到货”,所以不需要等待了。比方说,假定调用KeWaitForSingleObject()的目的是接收一个报文,但是这个报文在此之前就已到达了,那当然就不用再睡眠等待,而可以立即就返回。不过在返回之前要通过KiSatisfyObjectWait()把账销掉。另一个就是PsBlockThread()后面条件语句“if (Status != STATUS_KERNEL_APC)”里面的返回语句。PsBlockThread()是KeWaitForSingleObject()中最本质的操作。正是这个函数,在一般的情况下,将当前线程置于被阻塞(睡眠)状态并启动线程调度,直至当前线程因为所等待的条件得到满足而被唤醒,才从这个函数返回。但是也有例外,就是因为有APC请求而被警醒,此时被警醒的线程会执行APC函数,执行完以后就立即返回,并把Status的返回值设置成STATUS_KERNEL_APC。在这种情况下当然要再次执行PsBlockThread(),这就是为什么要把PsBlockThread()放在一个循环中的原因。
再看阻塞当前进程之前的准备工作。这里有两个重点。一个是对KiIsObjectSignaled()的调用、以及根据其结果作出的反应,这前面已经讲过了。另一个是对KiCheckAlertability()的调用。在调用NtWaitForSingleObject()的时候有个参数Alertable,表示是否允许本次等待因APC请求而中断,或者说“被警醒”。这个参数一路传了下来。
如果一个线程的睡眠/等待状态是“可警醒”的,那么:
? ● 别的线程可以通过系统调用NtAlertThread()将其警醒。
? ● APC请求的到来也可以将其警醒。
读者也许会想,既然当前线程已经睡眠,这APC请求从何而来呢?其实很简单。例如,一个线程可能通过系统调用NtWriteFile()启动了一次异步的文件操作,由于是异步操作,这个系统调用很快就返回了,然后这线程就因为进程间通信而辗转地进入了KeWaitForSingleObject()、并因此而被阻塞进入了睡眠。然而,这个进程所启动的异步操作仍在进行,当这异步操作最终完成时,内核会把预定的APC函数挂入这个线程的APC请求队列。这时候,目标线程的睡眠之是否可警醒,就显然会有不一样的效果。这里KiCheckAlertability()的作用是结合参数Alertable的值检查当前线程是否已经受到警醒,以及是否已经有APC请求,并依此进行必要的状态设置。
再看对于KWAIT_BLOCK数据结构的设置,其中的字段WaitType设置成WaitAny,与此相对的是WaitAll。在对于多个对象的等待中,前者表示其中只要有任何一个对象满足条件即可,后者则表示必须全部满足条件。不过KeWaitForSingleObject()所等待的是单一对象,所以二者其实并无不同,只是按编程的约定采用WaitAny。另一个字段WaitKey实际上就是当前状态,STATUS_WAIT_0表示正常的等待。
接着就通过InsertTailList()把已经准备好的KWAIT_BLOCK数据结构插入目标对象的WaitListHead队列。这样,一旦目标对象的状态发生变化,就可以顺着这个队列找到所有在这个对象上等待的线程。
下面对CurrentThread->Queue的检查和对于KiWakeQueue()的调用与设备驱动有关,而不在我们此刻关心的范围内,所以也把它跳过。
再往下就是调用PsBlockThread()使当前线程进入睡眠了。
[code][NtWaitForSingleObject() > KeWaitForSingleObject() > PsBlockThread()]
VOID
STDCALL
PsBlockThread(PNTSTATUS Status,
UCHAR Alertable,
ULONG WaitMode,
UCHAR WaitReason)
{
PKTHREAD Thread = KeGetCurrentThread();
PKWAIT_BLOCK WaitBlock;
if (Thread->ApcState.KernelApcPending) {
DPRINT("Dispatching Thread as ready (APC!)\n");
/* Remove Waits */
WaitBlock = Thread->WaitBlockList;
while (WaitBlock) {
RemoveEntryList (&WaitBlock->WaitListEntry);
WaitBlock = WaitBlock->NextWaitBlock;
}
Thread->WaitBlockList = NULL;
/* Dispatch it and return status */
PsDispatchThreadNoLock (THREAD_STATE_READY);
if (Status != NULL) *Status = STATUS_KERNEL_APC;
} else {
/* Set the Thread Data as Requested */
DPRINT("Dispatching Thread as blocked\n");
Thread->Alertable = Alertable;
Thread->WaitMode = (UCHAR)WaitMode;
Thread->WaitReason = WaitReason;
/* Dispatch it and return status */
PsDispatchThreadNoLock(THREAD_STATE_BLOCKED);
if (Status != NULL) *Status = Thread->WaitStatus;
}
DPRINT("Releasing Dispatcher Lock\n");
KfLowerIrql(Thread->WaitIrql);
}[/code]
这个函数没有什么特殊之处,值得一说的是它会检查是否有APC请求在等待执行,如果有的话就退出等待,不进入睡眠了。PsDispatchThreadNoLock()的作用是线程调度,作为参数传递给它的THREAD_STATE_READY和THREAD_STATE_BLOCKED分别表示目标线程的新的状态。THREAD_STATE_READY显然是“就绪”,也就是不进入睡眠。而THREAD_STATE_BLOCKED当然是“阻塞”、即睡眠。当然,如果进入睡眠的话,那就要到以后被唤醒(所等待的条件得到满足或超时)或警醒(APC请求的到来或受别的线程警醒)才会从这个函数返回。在这里,我们假定当前进程被阻塞。
至此,当前线程、即等待“到货”(进程间信息的到来)的线程已被阻塞进入了睡眠。下面要看的是当进程间信息到来时怎样唤醒这个线程了。
在Windows内核中,与KeWaitForSingleObject()相对的函数是KiWaitTest()。前者使一个线程在一个(可等待)对象上等待而被阻塞进入睡眠,后者则唤醒在一个对象上等待的所有线程。
[code]VOID FASTCALL
KiWaitTest(PDISPATCHER_HEADER Object, KPRIORITY Increment)
{
PLIST_ENTRY WaitEntry;
PLIST_ENTRY WaitList;
PKWAIT_BLOCK CurrentWaitBlock;
PKWAIT_BLOCK NextWaitBlock;
/* Loop the Wait Entries */
DPRINT("KiWaitTest for Object: %x\n", Object);
WaitList = &Object->WaitListHead;
WaitEntry = WaitList->Flink;
while ((WaitEntry != WaitList) && (Object->SignalState > 0)) {
/* Get the current wait block */
CurrentWaitBlock =
CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry);
/* Check the current Wait Mode */
if (CurrentWaitBlock->WaitType == WaitAny) {
/* Easy case, satisfy only this wait */
DPRINT("Satisfiying a Wait any\n");
WaitEntry = WaitEntry->Blink;
KiSatisfyObjectWait(Object, CurrentWaitBlock->Thread);
} else {
/* Everything must be satisfied */
DPRINT("Checking for a Wait All\n");
NextWaitBlock = CurrentWaitBlock->NextWaitBlock;
/* Loop first to make sure they are valid */
while (NextWaitBlock) {
/* Check if the object is signaled */
if (!KiIsObjectSignaled(Object, CurrentWaitBlock->Thread)) {
/* It's not, move to the next one */
DPRINT1("One of the object is non-signaled, sorry.\n");
goto SkipUnwait;
}
/* Go to the next Wait block */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -