?? 漫談兼容內核之十五: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 + -