⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 漫谈兼容内核之十五:windows线程的等待、唤醒机制.txt

📁 漫谈系统内核内幕 收集得很辛苦 呵呵 大家快下在吧
💻 TXT
📖 第 1 页 / 共 3 页
字号:
                NextWaitBlock = NextWaitBlock->NextWaitBlock;
            }
                      
            /* All the objects are signaled, we can satisfy */
            DPRINT("Satisfiying a Wait All\n");
            WaitEntry = WaitEntry->Blink;
            KiSatisifyMultipleObjectWaits(CurrentWaitBlock);
        }
       
        /* All waits satisfied, unwait the thread */
        DPRINT("Unwaiting the Thread\n");
        KiAbortWaitThread(CurrentWaitBlock->Thread,
                         CurrentWaitBlock->WaitKey, Increment);

SkipUnwait:
        /* Next entry */
        WaitEntry = WaitEntry->Flink;
    }
   
    DPRINT("Done\n");
}  [/code]            

    这个函数是针对一个特定对象的,所以参数就是指向其数据结构的指针(这一次的类型倒是DISPATCHER_HEADER指针)。另一个参数Increment说明是否、以及怎样、对被唤醒线程的优先级作出调整。
    程序的代码基本上就是一个while循环,这是对目标对象的等待队列中所有等待块即KWAIT_BLOCK数据结构的循环。额外的条件是目标对象数据结构中的SignalState字段必须大于0,其原因和意义我们后面就会看到。如前所述,队列中的每个KWAIT_BLOCK数据结构都代表着一个因为在这个对象上等待而被阻塞的线程。另一方面,所涉及的每个线程又都可能同时在多个对象上等待,而等待的类型(方式)则有WaitAny和WaitAll两种。如果只是在单一的对象上等待,则我们在前面看到所用的是WaitAny。
    如果队列中某个进程的等待方式是WaitAny,那么就对目标对象执行一次KiSatisfyObjectWait(),然后就通过KiAbortWaitThread()将其唤醒、使其结束等待。反之,要是这个线程的等待方式是WaitAll,则要通过另一个while循环扫描该线程的等待块队列,通过KiIsObjectSignaled()检查其所等待的每一个对象是否接受了“信号”(是否到了货)。只要其中有一个对象没有满足条件,这个线程就不满足被唤醒的条件,因而通过“goto SkipUnwait”语句跳过对此线程的处理。反之,如果所等待的所有对象都收到了“信号”,就对这些对象执行KiSatisifyMultipleObjectWaits(),那实际上就是对所有这些对象都执行KiSatisfyObjectWait()。
    前面讲过,KiSatisfyObjectWait()的作用是把目标对象上已经接收到的“信号”消耗掉,或者说对已经到货、并且有了“货主”(因而马上就要被领走)的货物进行销账处理。怎么销呢?我们看代码:

[code][KiWaitTest() > KiSatisfyObjectWait()]

VOID  FASTCALL
KiSatisfyObjectWait(PDISPATCHER_HEADER Object, PKTHREAD Thread)
{
    /* Special case for Mutants */
    if (Object->Type == MutantObject) {
    
        /* Decrease the Signal State */
        Object->SignalState--;
       
        /* Check if it's now non-signaled */
        if (Object->SignalState == 0) {
       
            /* Set the Owner Thread */
            ((PKMUTANT)Object)->OwnerThread = Thread;
           
            /* Disable APCs if needed */
            Thread->KernelApcDisable -= ((PKMUTANT)Object)->ApcDisable;
           
            /* Check if it's abandoned */
            if (((PKMUTANT)Object)->Abandoned) {
           
                /* Unabandon it */
                ((PKMUTANT)Object)->Abandoned = FALSE;
               
                /* Return Status */
                Thread->WaitStatus = STATUS_ABANDONED;
            }
           
            /* Insert it into the Mutant List */
            InsertHeadList(&Thread->MutantListHead,
                        &((PKMUTANT)Object)->MutantListEntry);
        }
   
    } else if ((Object->Type & TIMER_OR_EVENT_TYPE) == EventSynchronizationObject) {
   
        /* These guys (Syncronization Timers and Events) just get un-signaled */
        Object->SignalState = 0;
       
    } else if (Object->Type == SemaphoreObject) {

        /* These ones can have multiple signalings, so we only decrease it */
        Object->SignalState--;
    }
}[/code]
    表面上KiSatisfyObjectWait()似乎是个通用的程序,可以适用于任何可等待对象,但是实际上它的内部还是按不同的可等待对象分别加以处理的。我们以“信号量(Semaphore)”为例来说明其操作方式。对于信号量对象,其数据结构中的SignalState表示可用的资源数量,或者可以理解为筹码的数量。每当资源的提供者向此对象提供一个筹码时,就使SignalState加1,而每当资源的使用者消耗一个筹码时就使其减1。由于此时目标对象已经得到了所要求的资源,SignalState已经递加了计数,这里使其递减,就表示把所收到的资源消耗掉了。这也解释了前面KiWaitTest()中的while循环为什么有个条件“Object->SignalState > 0”,因为如果Object->SignalState不大于0就说明资源已经耗尽,再继续循环就入不敷出了。
    最后,把已经接收到的资源耗用掉以后,就要通过KiAbortWaitThread()唤醒正在等待此项资源的线程。读者也许会想,既然接收到的资源已经耗用掉,那正在目标进程上等待的线程还能得到什么呢?其实,这里的所谓耗用一般只是“账面”上的,与此相伴随的还有“真金白银”(例如报文),这才是等待中的线程所需要的。打个比方,目标对象得到“进货”以后,账面上就有了“库存”,而KiSatisfyObjectWait()就是把账上的库存销掉,而实际的货物则有待于要货的线程被唤醒以后前去领取。这一点,读者在读了下一篇关于进程间通信的漫谈以后就会更加清楚。
    我们接着看KiAbortWaitThread()的代码。注意这程序名中的“Abort”是指等待状态的Abort,而不是线程的Abort。

[code][KiWaitTest() > KiAbortWaitThread()]

VOID  FASTCALL
KiAbortWaitThread(PKTHREAD Thread, NTSTATUS WaitStatus, KPRIORITY Increment)
{
    PKWAIT_BLOCK WaitBlock;

    /* If we are blocked, we must be waiting on something also */
    . . . . . .
    ASSERT((Thread->State == THREAD_STATE_BLOCKED) ==
                                         (Thread->WaitBlockList != NULL));

    /* Remove the Wait Blocks from the list */
    DPRINT("Removing waits\n");
    WaitBlock = Thread->WaitBlockList;
    while (WaitBlock) {
        /* Remove it */
        DPRINT("Removing Waitblock: %x, %x\n", WaitBlock, WaitBlock->NextWaitBlock);
        RemoveEntryList(&WaitBlock->WaitListEntry);
       
        /* Go to the next one */
        WaitBlock = WaitBlock->NextWaitBlock;
    };
   
    /* Check if there's a Thread Timer */
    if (Thread->Timer.Header.Inserted) {
        /* Cancel the Thread Timer with the no-lock fastpath */
        DPRINT("Removing the Thread's Timer\n");
        Thread->Timer.Header.Inserted = FALSE;
        RemoveEntryList(&Thread->Timer.TimerListEntry);
    }
   
    /* Increment the Queue's active threads */
    if (Thread->Queue) {
        DPRINT("Incrementing Queue's active threads\n");
        Thread->Queue->CurrentCount++;
    }

    /* Reschedule the Thread */
    DPRINT("Unblocking the Thread\n");
    PsUnblockThread((PETHREAD)Thread, &WaitStatus, 0);
}[/code]

    这是一段简单直白的代码,如果略去对定时器(Timer)的处理以及对Thread->Queue的处理(如前所述,这与设备驱动有关),那就只剩下两件事。第一件事是通过一个while循环将目标线程的所有等待块WaitBlock从其所在的队列中脱离出来。之所以如此是因为目标进程可能同时在多个对象上等待,因此通过不同的WaitBlock挂入了不同对象的队列。不过,对于因执行KeWaitForSingleObject()而被阻塞的线程而言,实际上只挂入了一个对象的队列,所以WaitBlock->NextWaitBlock为0。第二件事就是通过PsUnblockThread()唤醒目标线程,对于熟悉Linux内核的读者这已是了无新意的事了。

    但是,等待中的线程被唤醒或警醒以后怎么走,倒是值得一说的。为此我们又要回到PsBlockThread()的代码中。在那里,睡眠中的目标线程被唤醒或警醒后从PsDispatchThreadNoLock()返回,此时目标线程的Thread->WaitStatus记录着返回时的状态,实际上也反映了返回的原因。返回的原因无非就是这么几种:
?  ● 条件得到满足而被唤醒,此时的Thread->WaitStatus为表示成功的状态码。
?  ● 超时或出错,此时的Thread->WaitStatus为相应的出错代码。
?  ● 别的线程对其执行了NtAlertThread()系统调用,此时的Thread->WaitStatus为STATUS_ALERTED。
?  ● 因APC请求而警醒,此时的Thread->WaitStatus为STATUS_KERNEL_APC。
    从代码中可以看出,从PsBlockThread()返回到KeWaitForSingleObject()时,通过参数Status返回的值就是Thread->WaitStatus。此外,也可能在PsBlockThread()中因发现有APC请求存在而根本就没有进入睡眠,此时返回的Status也是STATUS_KERNEL_APC。于是,当目标线程从PsBlockThread()返回到KeWaitForSingleObject()中时,如果Status为STATUS_KERNEL_APC就说明这是因为被APC请求警醒而返回的,原先的目的并未达到,还须再接再厉,所以才有KeWaitForSingleObject()中的do{}while(TRUE)循环。还要说明,由于APC机制的特殊安排,目标进程从睡眠时醒来时会先执行APC函数,然后才回到原来的轨道而从PsDispatchThreadNoLock()返回到PsBlockThread()。

    最后,由于本文实际上也涉及警醒,而不仅仅是唤醒,这里也看一下系统调用NtAlertThread()的代码:

[code]NTSTATUS  STDCALL
NtAlertThread (IN HANDLE ThreadHandle)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PETHREAD Thread;
    NTSTATUS Status;

    /* Reference the Object */
    Status = ObReferenceObjectByHandle(ThreadHandle,
                                       THREAD_SUSPEND_RESUME,
                                       PsThreadType,
                                       PreviousMode,
                                       (PVOID*)&Thread,
                                       NULL);
  
    /* Check for Success */
    if (NT_SUCCESS(Status)) {
  
        /*
         * Do an alert depending on the processor mode. If some kmode code wants to
         * enforce a umode alert it should call KeAlertThread() directly. If kmode
         * code wants to do a kmode alert it's sufficient to call it with Zw or just
         * use KeAlertThread() directly
         */
        KeAlertThread(&Thread->Tcb, PreviousMode);
  
        /* Dereference Object */
        ObDereferenceObject(Thread);
    }
   
    /* Return status */
    return Status;
}

    当然,参数ThreadHandle代表着已被打开的目标线程。显然,这里实质性的操作是KeAlertThread()。

[NtAlertThread() > KeAlertThread()]

BOOLEAN  STDCALL
KeAlertThread(PKTHREAD Thread, KPROCESSOR_MODE AlertMode)
{
    KIRQL OldIrql;
    BOOLEAN PreviousState;

    /* Acquire the Dispatcher Database Lock */
    OldIrql = KeAcquireDispatcherDatabaseLock();
  
    /* Save the Previous State */
    PreviousState = Thread->Alerted[AlertMode];
     
    /* Return if Thread is already alerted. */
    if (PreviousState == FALSE) {
      
        /* If it's Blocked, unblock if it we should */
        if (Thread->State == THREAD_STATE_BLOCKED &&
            (AlertMode == KernelMode || Thread->WaitMode == AlertMode) &&
            Thread->Alertable) {
           
            DPRINT("Aborting Wait\n");
            KiAbortWaitThread(Thread,
                        STATUS_ALERTED, THREAD_ALERT_INCREMENT);
      
        } else {
          
            /* If not, simply Alert it */
            Thread->Alerted[AlertMode] = TRUE;
        }
    }
   
    /* Release the Dispatcher Lock */
    KeReleaseDispatcherDatabaseLock(OldIrql);
   
    /* Return the old state */
    return PreviousState;
}[/code]
    这段代码就留给读者自己阅读了。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -