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

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

📁 漫谈系统内核内幕 收集得很辛苦 呵呵 大家快下在吧
💻 TXT
📖 第 1 页 / 共 3 页
字号:
       
        /* 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 + -