KeWaitForMultipleObjects是Windows内核中实现多对象等待的核心函数,其内部逻辑结构复杂且精巧。理解其执行流程,对于掌握Windows的并发编程与同步机制至关重要。本文将深入分析其伪代码所展现的逻辑结构。
函数逻辑总览
函数的主体由一个do...while(TRUE)无限循环构成。在循环开始时,会将IRQL提升至SYNCH_LEVEL,初始化线程本地变量并锁定调度器数据库。
do {
WaitStart:
Thread->WaitIrql = KeRaiseIrqlToSynchLevel();
InitializeWaitMultiple();
KiLockDispatcherDatabaseAtSynchLevel();
...
} while (TRUE);
第一部分:条件检查与立即满足判断
进入循环后,函数首先处理潜在的内核APC(异步过程调用)挂起情况,这在多处理器系统中需要特别注意。
if (Thread->ApcState.KernelApcPending &&
Thread->SpecialApcDisable == 0 &&
Thread->WaitIrql < APC_LEVEL) {
// 处理内核APC...
} else {
1.1 构建等待块并尝试立即满足等待
如果无需立即处理APC,函数会开始构建等待块,并立即检查等待条件是否已被满足。此过程根据WaitType(WaitAny或WaitAll)分为两种路径。
对于WaitAny类型,只要任一对象满足条件,等待即可完成。其核心逻辑如下:
Index = 0;
if (WaitType == WaitAny) {
do {
// 测试当前对象是否可立即满足等待条件
...
Index += 1;
} while(Index < Count);
} else {
对于WaitAll类型,则需要扫描所有对象。仅当所有对象均满足条件时,才会进入完成等待的流程。
do {
// 测试当前对象是否可立即满足等待条件
...
Index += 1;
} while(Index < Count);
// 只有当所有对象都被扫描且满足条件时
if (Index == Count) {
WaitBlock = &WaitBlockArray[0];
do {
Objectx = (PKMUTANT)WaitBlock->Object;
KiWaitSatisfyAny(Objectx, Thread);
WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != &WaitBlockArray[0]);
WaitStatus = (NTSTATUS)Thread->WaitStatus;
goto NoWait; // 跳转到清理并返回的代码段
}
} // if (WaitType == WaitAny) 结束
如果等待未能被立即满足,代码将继续执行。
第二部分:进入等待状态
当无法立即获得资源时,线程需要被置入等待状态。
2.1 检查可警报状态与超时设置
首先检查线程是否处于可警报状态Alertable,并处理可能的用户模式APC。
TestForAlertPending(Alertable);
接着检查是否指定了超时Timeout。如果超时值为0,函数会立即返回STATUS_TIMEOUT。
if (ARGUMENT_PRESENT(Timeout)) {
if (Timeout->QuadPart == 0) {
WaitStatus = (NTSTATUS)(STATUS_TIMEOUT);
goto NoWait;
}
}
2.2 将等待块插入对象等待链表
将当前线程的等待块插入到它所要等待的每一个内核对象的等待链表中,这是实现同步通知的关键步骤。
WaitBlock = &WaitBlockArray[0];
do {
Objectx = (PKMUTANT)WaitBlock->Object;
InsertTailList(&Objectx->Header.WaitListHead, &WaitBlock->WaitListEntry);
WaitBlock = WaitBlock->NextWaitBlock;
} while (WaitBlock != &WaitBlockArray[0]);
2.3 激活队列中的其他等待者(如果适用)
如果当前线程正在处理一个队列项,则尝试激活阻塞在该队列对象上的其他线程。
Queue = Thread->Queue;
if (Queue != NULL) {
KiActivateWaiterQueue(Queue);
}
2.4 设置线程状态并插入处理器等待链表
正式将线程状态设置为Waiting,并根据是否可交换栈StackSwappable将其插入当前处理器Prcb的等待链表。
CurrentPrcb = KeGetCurrentPrcb();
Thread->State = Waiting;
if (StackSwappable != FALSE) {
InsertTailList(&CurrentPrcb->WaitListHead, &Thread->WaitListEntry);
}
2.5 执行线程切换
设置上下文交换繁忙标志,解锁调度器数据库,并调用KiSwapThread切换到另一个就绪线程。这是系统级别进行线程调度的核心时刻。
KiSetContextSwapBusy(Thread);
KiUnlockDispatcherDatabaseFromSynchLevel();
WaitStatus = (NTSTATUS)KiSwapThread(Thread, CurrentPrcb);
2.6 线程被唤醒后的处理
当KiSwapThread函数返回时,意味着线程已被唤醒。首先检查是否因为要传递内核APC而唤醒。
if (WaitStatus != STATUS_KERNEL_APC) {
return WaitStatus; // 等待条件达成或超时,直接返回状态
}
如果是因为内核APC而唤醒,则需要重新计算剩余的超时时间(如果指定了超时),然后跳回WaitStart标签处,开始新一轮的等待循环。
if (ARGUMENT_PRESENT(Timeout)) {
Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime);
}
} // 外层 if (Thread->ApcState.KernelApcPending && 结束
// 这里会跳转回 `WaitStart` 或继续循环
通过以上分析可以看出,KeWaitForMultipleObjects通过一个精心设计的循环和状态机,高效地处理了多对象同步中的立即返回、超时控制、线程切换以及APC中断等复杂场景,是Windows内核并发控制的基石之一。