[9]Windows内核情景分析 --- DPC

DPC不同APC,DPC的全名是‘延迟过程调用’。

DPC最初作用是设计为中断服务程序的一部分。因为每次触发中断,都会关中断,然后执行中断服务例程。由于关中断了,所以中断服务例程必须短小精悍,不能消耗过多时间,否则会导致系统丢失大量其他中断。但是有的中断,其中断服务例程要做的事情本来就很多,那怎么办?于是,可以在中断服务例程中先执行最紧迫的那部分工作,然后把剩余的相对来说不那么重要的工作移入到DPC函数中去执行。因此,DPC又叫ISR的后半部。(比如每次时钟中断后,其isr会扫描系统中的所有定时器是否到点,若到点就调用各定时器的函数。但是这个扫描过程比较耗时,因此,时钟中断的isr会将扫描工作纳入dpc中进行)

每当触发一个中断时,中断服务例程可以在当前cpu中插入一个DPC,当执行完isr,退出isr后, cpu就会扫描它的dpc队列,依次执行里面的每个dpc,当执行完dpc后,才又回到当前线程的中断处继续执行。

typedef struct _KDPC {

  UCHAR Type; //DPC类型(分为普通DPC和线程化DPC)

  UCHAR Importance;//该DPC的重要性,将决定挂在队列头还是尾

  volatile USHORT Number;//第5位为0就表示当前cpu,否则,最低4位表示目标cpu号

  LIST_ENTRY DpcListEntry;//用来挂入dpc链表

  PKDEFERRED_ROUTINE DeferredRoutine;//dpc函数

  PVOID DeferredContext;//dpc函数的参数

  PVOID SystemArgument1;//挂入时的系统附加参数1

  PVOID SystemArgument2;//挂入时的系统附加参数2

  volatile PVOID DpcData;//所在的dpc队列

} KDPC, *PKDPC, *RESTRICTED_POINTER PRKDPC;

VOID

KeInitializeDpc(IN PKDPC Dpc,//DPC对象(DPC也是一种内核对象)

                IN PKDEFERRED_ROUTINE DeferredRoutine, //DPC函数

                IN PVOID DeferredContext)//DPC函数的参数

{

    KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject);

}

VOID

KiInitializeDpc(IN PKDPC Dpc,

                IN PKDEFERRED_ROUTINE DeferredRoutine,

                IN PVOID DeferredContext,

                IN KOBJECTS Type) 

{

    Dpc->Type = Type;

    Dpc->Number = 0;//初始的目标cpu为当前cpu

    Dpc->Importance= MediumImportance;

    Dpc->DeferredRoutine = DeferredRoutine;

    Dpc->DeferredContext = DeferredContext;

    Dpc->DpcData = NULL;//表示该DPC尚未挂入任何DPC队列

}

DPC初始化构造时的目标cpu默认都是当前cpu。

BOOLEAN  KeInsertQueueDpc(IN PKDPC Dpc,IN PVOID SystemArgument1,IN PVOID SystemArgument2)

{

    KIRQL OldIrql;

    PKPRCB Prcb, CurrentPrcb;

    ULONG Cpu;

    PKDPC_DATA DpcData;

    BOOLEAN DpcConfigured = FALSE, DpcInserted = FALSE;

    KeRaiseIrql(HIGH_LEVEL, &OldIrql);//插入过程的中断级是最高的,这个过程不会被打断。

    CurrentPrcb = KeGetCurrentPrcb();

    //检查目标cpu号的第5位为1(32 = 00100000),就表示其它cpu,低4位表示cpu号

    if (Dpc->Number >= 32)

    {

        Cpu = Dpc->Number - 32;

        Prcb = KiProcessorBlock[Cpu];

    }

    Else //否则,表示当前cpu

{

Cpu = Prcb->Number;

        Prcb = CurrentPrcb;//目标cpu就是当前cpu

    }

//if 要插入的是一个线程化dpc并且那个cpu启用了线程化dpc机制

    if ((Dpc->Type == ThreadedDpcObject) && (Prcb->ThreadDpcEnable))

        DpcData = &Prcb->DpcData[DPC_THREADED]; //目标cpu的线程化dpc队列

    else

        DpcData = &Prcb->DpcData[DPC_NORMAL];//目标cpu的普通dpc队列

KiAcquireSpinLock(&DpcData->DpcLock);

//if 尚未挂入任何dpc队列

    if (!InterlockedCompareExchangePointer(&Dpc->DpcData, DpcData, NULL))

    {

        Dpc->SystemArgument1 = SystemArgument1;

        Dpc->SystemArgument2 = SystemArgument2;

        DpcData->DpcQueueDepth++;

        DpcData->DpcCount++;

        DpcConfigured = TRUE;

        //不管如何,先把dpc挂到目标cpu的dpc队列中

        if (Dpc->Importance == HighImportance)

            InsertHeadList(&DpcData->DpcListHead, &Dpc->DpcListEntry);

        else

            InsertTailList(&DpcData->DpcListHead, &Dpc->DpcListEntry);

        if (&Prcb->DpcData[DPC_THREADED] == DpcData)

        {

            if (!(Prcb->DpcThreadActive) && !(Prcb->DpcThreadRequested))

                线程化DPC,ReactOS目前尚不支持,略

         

        }

        Else //若挂入的是一个普通dpc(最常见),检查是否需要立即发出一个dpc软中断给cpu

        {

            //if 目标cpu当前没在执行dpc,并且它尚未收到dpc中断请求

            if (!(Prcb->DpcRoutineActive) && !(Prcb->DpcInterruptRequested))

            {

                if (Prcb != CurrentPrcb)//if 目标cpu是其它cpu

                {

                    if (((Dpc->Importance == HighImportance) ||

                        (DpcData->DpcQueueDepth >= Prcb->MaximumDpcQueueDepth))

 &&

                        (!(AFFINITY_MASK(Cpu) & KiIdleSummary) || (Prcb->Sleeping)))

                    {

                        Prcb->DpcInterruptRequested = TRUE; 

                        DpcInserted = TRUE; //表示需要立即给cpu发出dpc软中断

                    }

                }

                Else //if 目标cpu就是自身cpu,最常见

                {

                    //一般插入的都是中等重要性的dpc,因此,一般会立即发出一个dpc中断。

                    if ((Dpc->Importance != LowImportance) ||

                        (DpcData->DpcQueueDepth >= Prcb->MaximumDpcQueueDepth) ||

                        (Prcb->DpcRequestRate < Prcb->MinimumDpcRate))

                    {

                        Prcb->DpcInterruptRequested = TRUE; 

                        DpcInserted = TRUE; //表示需要立即给cpu发出dpc软中断

                    }

                }

            }

        }

    }

    KiReleaseSpinLock(&DpcData->DpcLock);

    if (DpcInserted)//if 需要立即发出一个dpc软中断

    {

        if (Prcb != CurrentPrcb)

            KiIpiSend(AFFINITY_MASK(Cpu), IPI_DPC);

        else

            HalRequestSoftwareInterrupt(DISPATCH_LEVEL);//给当前cpu发出一个dpc软中断

    }

    KeLowerIrql(OldIrql);//降低irql

    return DpcConfigured;

}

如上,这个函数将dpc挂入目标cpu的指定dpc队列(每个cpu有两个dpc队列,一个普通的,一个线程化的)。然后检查当前是否可以立即向目标cpu发出一个dpc软中断,这样,以在下次降低到DISPATCH_LEVEL以下时扫描执行dpc(其实上面的这个函数一般用于在isr中调用,但用户也可以随时手动调用)

(一般来说,挂入的都是中等重要性的dpc,一般在dpc进队的同时就会顺势发出一个dpc中断.)

 

下面的函数可用于模拟硬件,向cpu发出任意irql级别的软中断,请求cpu处理执行那种中断。

VOID FASTCALL

HalRequestSoftwareInterrupt(IN KIRQL Irql)//Irql一般是APC_LEVEL/DPC_LEVEL

{

    ULONG EFlags;

    PKPCR Pcr = KeGetPcr();

    KIRQL PendingIrql;

    EFlags = __readeflags();//保存老的eflags寄存器

    _disable();//关中断

    Pcr->IRR |= (1 << Irql);//关键。标志向cpu发出了一个对应irql级的软中断

PendingIrql = SWInterruptLookUpTable[Pcr->IRR & 3];//IRR后两位表示是否有阻塞的apc中断

//若有阻塞的apc中断,并且当前irql是PASSIVE_LEVEL,立即执行apc。也即在PASSIVE_LEVEL级时发出任意软中断后,会立即检查执行现有的apc中断。

if (PendingIrql > Pcr->Irql)

 SWInterruptHandlerTable[PendingIrql]();//调用执行apc中断的isr,处理apc中断

    __writeeflags(EFlags);//恢复原eflags寄存器

}

DPC函数的执行时机:

我们看一个函数

VOID FASTCALL

KfLowerIrql(IN KIRQL OldIrql)//降回到原irql

{

    ULONG EFlags;

    ULONG PendingIrql, PendingIrqlMask;

    PKPCR Pcr = KeGetPcr();

    PIC_MASK Mask;

    EFlags = __readeflags();//保存老的eflags寄存器

    _disable();//关中断

    Pcr->Irql = OldIrql;//降低到指定irql

    

    //检查是否有在目标irql以上的阻塞中的软中断

    PendingIrqlMask = Pcr->IRR & FindHigherIrqlMask[OldIrql];

    if (PendingIrqlMask)

{   

    //从高位到低位扫描,找到阻塞中的最高irql级的软中断

        BitScanReverse(&PendingIrql, PendingIrqlMask); 

        if (PendingIrql > DISPATCH_LEVEL) …

            SWInterruptHandlerTable[PendingIrql]();//处理那个软中断,似乎这儿有问题,应该是

            SWInterruptHandlerTable[31-PendingIrql]();

    }

    __writeeflags(EFlags);//恢复原eflags寄存器

}

unsigned char BitScanReverse(ULONG * const Index, unsigned long Mask)

{

    *Index = 0;

    while (Mask && ((Mask & (1 << 31)) == 0))

    {

        Mask <<= 1;

        ++(*Index);

    }

    return Mask ? 1 : 0;

}

各个软中断的处理函数如下:(怀疑这个表的布局有问题)

PHAL_SW_INTERRUPT_HANDLER SWInterruptHandlerTable[20] =

{

    KiUnexpectedInterrupt,//PASSIVE_LEVEL最低了,永远不会中断别的irql

    HalpApcInterrupt,//APC中断的isr

    HalpDispatchInterrupt2,//DPC中断的isr

    KiUnexpectedInterrupt,

    HalpHardwareInterrupt0,

    HalpHardwareInterrupt1,

    HalpHardwareInterrupt2,

    HalpHardwareInterrupt3,

    HalpHardwareInterrupt4,

    HalpHardwareInterrupt5,

    HalpHardwareInterrupt6,

    HalpHardwareInterrupt7,

    HalpHardwareInterrupt8,

    HalpHardwareInterrupt9,

    HalpHardwareInterrupt10,

    HalpHardwareInterrupt11,

    HalpHardwareInterrupt12,

    HalpHardwareInterrupt13,

    HalpHardwareInterrupt14,

    HalpHardwareInterrupt15

};

下面是处理DPC软中断的isr

VOID  HalpDispatchInterrupt2(VOID)

{

    ULONG PendingIrqlMask, PendingIrql;

    KIRQL OldIrql;

    PIC_MASK Mask;

PKPCR Pcr = KeGetPcr();

//这个函数里面会提高irql到DISPACH_LEVEL去扫描执行dpc队列中的所有dpc

    OldIrql = _HalpDispatchInterruptHandler();//关键函数

Pcr->Irql = OldIrql;//恢复成原来的irql

//再去检测是否仍有更高irql的阻塞软中断

    PendingIrqlMask = Pcr->IRR & FindHigherIrqlMask[OldIrql];

    if (PendingIrqlMask)

    {

        BitScanReverse(&PendingIrql, PendingIrqlMask);

        if (PendingIrql > DISPATCH_LEVEL) …

            SWInterruptHandlerTable[PendingIrql]();//应该是[31 - PendingIrql]

    }

}

KIRQL _HalpDispatchInterruptHandler(VOID)

{

    KIRQL CurrentIrql;

    PKPCR Pcr = KeGetPcr();

    CurrentIrql = Pcr->Irql;

    Pcr->Irql = DISPATCH_LEVEL;//将irql临时提高到DISPATCH_LEVEL

    Pcr->IRR &= ~(1 << DISPATCH_LEVEL);//清除对应的软中断位

    _enable();//开中断

    KiDispatchInterrupt();//关键函数。开中断后扫描执行所有dpc

    _disable();

    return CurrentIrql;//返回原irql

}

//下面的函数扫描当前cpu的dpc队列执行所有dpc

KiDispatchInterrupt

{

    push ebx

    mov ebx, PCR[KPCR_SELF]  //ebc = kpcr*

    cli //关中断

    //检查dpc队列是否为空

    mov eax, [ebx+KPCR_PRCB_DPC_QUEUE_DEPTH]

    or eax, [ebx+KPCR_PRCB_TIMER_REQUEST]

    or eax, [ebx+KPCR_PRCB_DEFERRED_READY_LIST_HEAD]

    jz CheckQuantum

    push ebp  //保存

    push dword ptr [ebx+KPCR_EXCEPTION_LIST] //保存原seh

    mov dword ptr [ebx+KPCR_EXCEPTION_LIST], -1  //将当前色seh置空

    /* Save the stack and switch to the DPC Stack */

    mov edx, esp

    mov esp, [ebx+KPCR_PRCB_DPC_STACK]  //切换为DPC函数专用的内核栈

    push edx //保存原来的内核栈顶位置

    mov ecx, [ebx+KPCR_PRCB]

    call @KiRetireDpcList@4      //关键。扫描执行dpc

    pop esp //恢复成原来的内核栈顶

    pop dword ptr [ebx+KPCR_EXCEPTION_LIST] //恢复

    pop ebp  //恢复

CheckQuantum:

    Sti //开中断

    cmp byte ptr [ebx+KPCR_PRCB_QUANTUM_END], 0 //现在回头检查当前线程的时间片是否耗尽

    jnz QuantumEnd //若已耗尽,直接跳到QuantumEnd处执行线程切换

    cmp byte ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0  //再检查当前是否有一个抢占者线程

    je Return

    /* Make space on the stack to save registers */

    sub esp, 3 * 4

    mov [esp+8], esi //保存

    mov [esp+4], edi //保存

    mov [esp+0], ebp //保存

    mov edi, [ebx+KPCR_CURRENT_THREAD]

#ifdef CONFIG_SMP //if多处理器

    call _KeRaiseIrqlToSynchLevel@0 //提升irql到SynchLevel

    mov byte ptr [edi+KTHREAD_SWAP_BUSY], 1 //标记该线程正在进行切换

    /* Acquire the PRCB Lock */

    lock bts dword ptr [ebx+KPCR_PRCB_PRCB_LOCK], 0

    jnb GetNext

    lea ecx, [ebx+KPCR_PRCB_PRCB_LOCK]

    call @KefAcquireSpinLockAtDpcLevel@4

#endif

GetNext:

    mov esi, [ebx+KPCR_PRCB_NEXT_THREAD]

    and dword ptr [ebx+KPCR_PRCB_NEXT_THREAD], 0

    mov [ebx+KPCR_CURRENT_THREAD], esi

    mov byte ptr [esi+KTHREAD_STATE_], Running

    mov byte ptr [edi+KTHREAD_WAIT_REASON], WrDispatchInt

    mov ecx, edi

    lea edx, [ebx+KPCR_PRCB_DATA]

    call @KiQueueReadyThread@8

    mov cl, APC_LEVEL

    call @KiSwapContextInternal@0

#ifdef CONFIG_SMP

    mov cl, DISPATCH_LEVEL

    call @KfLowerIrql@4

#endif

    mov ebp, [esp+0] //恢复

    mov edi, [esp+4] //恢复

    mov esi, [esp+8] //恢复

    add esp, 3*4

Return:

    pop ebx

    ret

QuantumEnd:

    mov byte ptr [ebx+KPCR_PRCB_QUANTUM_END], 0

    call _KiQuantumEnd@0  //调用这个函数切换线程

    pop ebx

    ret

}

上面的函数在执行dpc前会先将内核栈切换为dpc函数专用栈。因为dpc函数运行在任意线程的上下文中,而dpc函数可能太大,局部变量太多而占用了过多的内核栈,所以需要为dpc函数的执行专门配备一个栈。

这个函数还有一个注意地方,就是在扫描dpc队列执行完所有dpc函数后,会检查当前线程的时间片是否耗尽,若耗尽就进行线程切换,若尚未耗尽,就检查当前是否有一个抢占者线程,若有,也进行线程切换。

【总之:系统在每次扫描执行完dpc队列后,都会尝试进行线程切换】

 

下面的函数才是最终扫描执行dpc的

VOID FASTCALL

KiRetireDpcList(IN PKPRCB Prcb)

{

    PKDPC_DATA DpcData;

    PLIST_ENTRY ListHead, DpcEntry;

    PKDPC Dpc;

    PKDEFERRED_ROUTINE DeferredRoutine;

    PVOID DeferredContext, SystemArgument1, SystemArgument2;

    ULONG_PTR TimerHand;

    DpcData = &Prcb->DpcData[DPC_NORMAL];//当前cpu的普通dpc队列

    ListHead = &DpcData->DpcListHead;

    do

    {

        Prcb->DpcRoutineActive = TRUE;//标记当前cpu正在执行dpc

        if (Prcb->TimerRequest) //if收到有定时器到期dpc中断(定时器是一种特殊的dpc)

        {

            TimerHand = Prcb->TimerHand;

            Prcb->TimerRequest = 0;

            _enable();

            KiTimerExpiration(NULL, NULL, (PVOID)TimerHand, NULL);//处理定时器队列

            _disable();

        }

        while (DpcData->DpcQueueDepth != 0)//遍历dpc队列

        {

            KeAcquireSpinLockAtDpcLevel(&DpcData->DpcLock);

            DpcEntry = ListHead->Flink;

            if (DpcEntry != ListHead)

            {

                RemoveEntryList(DpcEntry);//取下来

                Dpc = CONTAINING_RECORD(DpcEntry, KDPC, DpcListEntry);

                Dpc->DpcData = NULL;

                DeferredRoutine = Dpc->DeferredRoutine;

                DeferredContext = Dpc->DeferredContext;

                SystemArgument1 = Dpc->SystemArgument1;

                SystemArgument2 = Dpc->SystemArgument2;

                DpcData->DpcQueueDepth--;

                Prcb->DebugDpcTime = 0;

                KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);

                _enable();//开中断

                //关键。执行DPC例程

                DeferredRoutine(Dpc,DeferredContext,SystemArgument1,SystemArgument2);

                ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

                _disable();//关中断

            }

            else

            {

                ASSERT(DpcData->DpcQueueDepth == 0);//肯定执行完了所有dpc

                KeReleaseSpinLockFromDpcLevel(&DpcData->DpcLock);

            }

        }

        Prcb->DpcRoutineActive = FALSE;//当前不再用dpc正在执行

        Prcb->DpcInterruptRequested = FALSE;

    } while (DpcData->DpcQueueDepth != 0);

}

注意:DPC函数运行在DISPATCH_LEVEL,并且开中断,因此dpc函数本身又可能被其他中断给中断。

因为dpc函数本身就是一种软中断,因此它支持中断嵌套。

综上:

DPC作为一种软中断,也是在irql的降低过程中得到执行的,并且是在从DISPATCH_LEVEL以上(不包括)降低到DISPATCH_LEVEL以下(不包括),也即在穿越DISPATCH_LEVEL的过程中,系统会先暂停在DISPATCH_LEVEL级执行当前cpu的dpc队列中所有阻塞的dpc,执行完后,再降回到真正的irql。

【总之:在降低过程中检查是否有dpc中断,若有执行之】一句口诀:【降低、检断、DPC】

不像APC的执行时机有很多,DPC的执行时机就一处。

那么在什么时候,系统会降低irql呢?除了用户显式调用这个内核函数外,isr一般工作在比DISPATCH_LEVEL高的irql,当isr退出时,必然会降低irql到原来的irql。因此,常常在isr中插入一个dpc到dpc队列,发出一个dpc中断给cpu,然后退出isr时,降低irql,顺理成章的执行dpc。

 

KeInitializeDpc初始化的dpc,默认的目标cpu都是当前cpu,如果需要将dpc发给其他cpu,让其在其他cpu上运行的话,可以采用下面的函数

VOID

KeSetTargetProcessorDpc(IN PKDPC Dpc,IN CCHAR Number)

{

    Dpc->Number = Number + 32;

}

这是一个非常有用的函数,因为他可以使你的代码运行在你想要的cpu上。比如,你写了一个函数,你只想那个函数运行在3号cpu上,那么,你可以构造一个在3号cpu上运行的dpc,然后在dpc里调用你自己的函数。这种技术常用于保障内联hook的多cpu线程安全 和 IDT hook。

当然也可使用KeSetSystemAffinityThread这个内核函数,修改当前线程的cpu亲缘性为‘只能运行在目标cpu’上,这样,也会立即导致当前线程立刻挪到其它cpu上去运行,KeSetSystemAffinityThread的代码,有兴趣的读者自己看。

 

本篇既然谈到了DPC,那就要讲下与之紧密相关的另一个话题:‘系统工作者线程’。

DPC函数是运行在DISPATCH_LEVEL的,而内核中的绝大多数函数的运行时irql都不能处在这个中断级别,如ZwCreateFie,ddk文档规定了,这个内核函数必须运行在PASSIVE_LEVEL,如果我们需要在某个DPC函数中调用ZwCreateFie,怎么办呢?一个办法便是将这个工作打包成一条工作项委派给‘系统工作者线程’去执行。

内核中有一个守护线程(其实分成9个线程),运行在PASSIVE_LEVEL,专门用来提供服务,执行别的线程委派给它的工作,这个守护线程就是‘系统工作者线程’。

 

按照工作项的紧迫程度,分成三种。系统中相应的有三种工作项队列

typedef enum _WORK_QUEUE_TYPE {

  CriticalWorkQueue,//较紧迫

  DelayedWorkQueue,//最常见

  HyperCriticalWorkQueue,//最紧迫

} WORK_QUEUE_TYPE;

CriticalWorkQueue工作项队列上配有5个服务线程,DelayedWorkQueue队列上配有3个服务线程,HyperCriticalWorkQueue上配有1个服务线程。

 

下面的宏用来构造一条工作项

#define ExInitializeWorkItem(Item,Routine,Context) \

{ \

   Item->WorkRoutine=Routine;\

   Item->Parameter=Context;\

   Item->List.Flink=NULL;\

}

构造好一条工作项后,就可以把这条工作项挂入指定紧迫程度的系统工作项队列中。

VOID

ExQueueWorkItem(IN PWORK_QUEUE_ITEM WorkItem,

                IN WORK_QUEUE_TYPE QueueType)//工作项紧迫程度类型

{

    PEX_WORK_QUEUE WorkQueue = &ExWorkerQueue[QueueType];//相应的工作项队列

    if ((ULONG_PTR)WorkItem->WorkerRoutine < MmUserProbeAddress)//必须位于内核空间

    {

        KeBugCheckEx(WORKER_INVALID,1, (ULONG_PTR)WorkItem,

                     (ULONG_PTR)WorkItem->WorkerRoutine,0);

}

KeInsertQueue(&WorkQueue->WorkerQueue, &WorkItem->List);//关键。

    if ((WorkQueue->Info.MakeThreadsAsNecessary) &&

        (!IsListEmpty(&WorkQueue->WorkerQueue.EntryListHead)) &&

        (WorkQueue->WorkerQueue.CurrentCount < WorkQueue->WorkerQueue.MaximumCount) &&

        (WorkQueue->DynamicThreadCount < 16))

    {

        KeSetEvent(&ExpThreadSetManagerEvent, 0, FALSE);

    }

}

当把工作项插入到系统对应的工作项队列后,系统中的某个服务线程便会在某一时刻处理该工作项。

9个服务线程的函数都是同一个函数,只是参数不同。我们看:

VOID ExpWorkerThreadEntryPoint(IN PVOID Context)//context参数主要表示工作项队列类型

{

    PLARGE_INTEGER TimeoutPointer = NULL;

    PETHREAD Thread = PsGetCurrentThread();

    if ((ULONG_PTR)Context & EX_DYNAMIC_WORK_THREAD)

    {

        Timeout.QuadPart = Int32x32To64(10, -10000000 * 60);//1分钟

        TimeoutPointer = &Timeout;

    }

    WorkQueueType = (WORK_QUEUE_TYPE)((ULONG_PTR)Context &~ EX_DYNAMIC_WORK_THREAD);

    WorkQueue = &ExWorkerQueue[WorkQueueType];

    WaitMode = (UCHAR)WorkQueue->Info.WaitMode;

    ASSERT(Thread->ExWorkerCanWaitUser == FALSE);

    if (WaitMode == UserMode) Thread->ExWorkerCanWaitUser = TRUE;

    if (!ExpWorkersCanSwap) KeSetKernelStackSwapEnable(FALSE);

    do

    {

        if (WorkQueue->Info.QueueDisabled)

        {

            KeSetKernelStackSwapEnable(TRUE);

            PsTerminateSystemThread(STATUS_SYSTEM_SHUTDOWN);//立即终止

        }

        OldValue = WorkQueue->Info;

        NewValue = OldValue;

        NewValue.WorkerCount++;//递增该队列上的工作线程计数

    }

    while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,*(PLONG)&NewValue,

                                      *(PLONG)&OldValue) != *(PLONG)&OldValue);

    Thread->ActiveExWorker = TRUE;//标记正式成为一个工作者线程了

ProcessLoop:

    for (;;)

{

    //等待本服务线程的工作项队列中出现工作项,然后取下来

        QueueEntry = KeRemoveQueue(&WorkQueue->WorkerQueue,WaitMode,TimeoutPointer);

        if ((NTSTATUS)(ULONG_PTR)QueueEntry == STATUS_TIMEOUT)

 break;//等待超时就退出for循环

        InterlockedIncrement((PLONG)&WorkQueue->WorkItemsProcessed);//递增已处理计数

        WorkItem = CONTAINING_RECORD(QueueEntry, WORK_QUEUE_ITEM, List);

        WorkItem->WorkerRoutine(WorkItem->Parameter);//关键。调用执行工作项

        if (Thread->Tcb.SpecialApcDisable)  Thread->Tcb.SpecialApcDisable = FALSE;

        //我们的工作项函数运行在PASSIVE_LEVEL,内部不要修改irql,否则蓝屏

        if (KeGetCurrentIrql() != PASSIVE_LEVEL)

        {

            KeBugCheckEx(WORKER_THREAD_RETURNED_AT_BAD_IRQL,

                         (ULONG_PTR)WorkItem->WorkerRoutine,KeGetCurrentIrql(),

                         (ULONG_PTR)WorkItem->Parameter, (ULONG_PTR)WorkItem);

        }

        if (Thread->ActiveImpersonationInfo)//工作项函数内部不能冒用令牌

        {

            KeBugCheckEx(IMPERSONATING_WORKER_THREAD, (ULONG_PTR)WorkItem->WorkerRoutine,

                         (ULONG_PTR)WorkItem->Parameter, (ULONG_PTR)WorkItem,0);

        }

    }

    if (!IsListEmpty(&Thread->IrpList)) goto ProcessLoop;  //继续服务

    if (WorkQueue->Info.QueueDisabled) goto ProcessLoop;   //继续服务

    //下面退出服务线程

    do

    {

        OldValue = WorkQueue->Info;

        NewValue = OldValue;

        NewValue.WorkerCount--;//递减该队列上的服务线程计数

    }

    while (InterlockedCompareExchange((PLONG)&WorkQueue->Info,*(PLONG)&NewValue,

                                      *(PLONG)&OldValue) != *(PLONG)&OldValue);

    InterlockedDecrement(&WorkQueue->DynamicThreadCount);

    Thread->ActiveExWorker = FALSE;

    KeSetKernelStackSwapEnable(TRUE);

    return;

}

上面这段代码想必不用过多解释了

posted @ 2018-12-18 02:05  jadeshu  阅读(973)  评论(0编辑  收藏  举报