﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-To Be C++-随笔分类-Windows Kernel</title><link>http://www.cppblog.com/shaker/category/16823.html</link><description>shaker's Blog&lt;br&gt;生当作人杰，死亦为鬼雄，至今思项羽，不肯过江东。</description><language>zh-cn</language><lastBuildDate>Thu, 15 Sep 2011 16:37:09 GMT</lastBuildDate><pubDate>Thu, 15 Sep 2011 16:37:09 GMT</pubDate><ttl>60</ttl><item><title>LISTVIEW_LButtonDown</title><link>http://www.cppblog.com/shaker/archive/2011/09/15/155890.html</link><dc:creator>shaker(太子)</dc:creator><author>shaker(太子)</author><pubDate>Thu, 15 Sep 2011 13:45:00 GMT</pubDate><guid>http://www.cppblog.com/shaker/archive/2011/09/15/155890.html</guid><wfw:comment>http://www.cppblog.com/shaker/comments/155890.html</wfw:comment><comments>http://www.cppblog.com/shaker/archive/2011/09/15/155890.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/shaker/comments/commentRss/155890.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/shaker/services/trackbacks/155890.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;<a href='http://www.cppblog.com/shaker/archive/2011/09/15/155890.html'>阅读全文</a><img src ="http://www.cppblog.com/shaker/aggbug/155890.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/shaker/" target="_blank">shaker(太子)</a> 2011-09-15 21:45 <a href="http://www.cppblog.com/shaker/archive/2011/09/15/155890.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[zt] Windows APC机制</title><link>http://www.cppblog.com/shaker/archive/2011/05/05/145733.html</link><dc:creator>shaker(太子)</dc:creator><author>shaker(太子)</author><pubDate>Thu, 05 May 2011 04:09:00 GMT</pubDate><guid>http://www.cppblog.com/shaker/archive/2011/05/05/145733.html</guid><wfw:comment>http://www.cppblog.com/shaker/comments/145733.html</wfw:comment><comments>http://www.cppblog.com/shaker/archive/2011/05/05/145733.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/shaker/comments/commentRss/145733.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/shaker/services/trackbacks/145733.html</trackback:ping><description><![CDATA[<p>异步过程调用(APCs) 是NT异步处理体系结构中的一个基础部分，理解了它，对于了解NT怎样操作和执行几个核心的系统操作很有帮助。</p>
<p>1) APCs允许用户程序和系统元件在一个进程的地址空间内某个线程的上下文中执行代码。<br>2) I/O管理器使用APCs来完成一个线程发起的异步的I/O操作。例如：当一个设备驱动调用IoCompleteRequest来通知I/O管理器，它已经结束处理一个异步I/O请求时，I/O管理器排队一个apc到发起请求的线程。然后线程在一个较低IRQL级别，来执行APC. APC的作用是从系统空间拷贝I/O操作结果和状态信息到线程虚拟内存空间的一个缓冲中。<br>3) 使用APC可以得到或者设置一个线程的上下文和挂起线程的执行。</p>
<p>上面是网上找来的，下面是MSDN上的说明：</p>
<p><br>An asynchronous procedure call (APC) is a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread, the system issues a software interrupt. The next time the thread is scheduled, it will run the APC function. An APC generated by the system is called a kernel-mode APC. An APC generated by an application is called a user-mode APC. A thread must be in an alertable state to run a user-mode APC.</p>
<p>Each thread has its own APC queue. An application queues an APC to a thread by calling the QueueUserAPC function. The calling thread specifies the address of an APC function in the call to QueueUserAPC. The queuing of an APC is a request for the thread to call the APC function.</p>
<p>还先看一下那些重要结构：</p>
<p>kd&gt; dt KTHREAD<br>nt!KTHREAD<br>+0x000 Header : _DISPATCHER_HEADER<br>+0x010 MutantListHead : _LIST_ENTRY<br>+0x018 InitialStack : Ptr32 Void<br>+0x01c StackLimit : Ptr32 Void<br>+0x020 KernelStack : Ptr32 Void<br>+0x024 ThreadLock : Uint4B<br>+0x028 ApcState : _KAPC_STATE<br>+0x028 ApcStateFill : [23] UChar<br>+0x03f ApcQueueable : UChar<br>+0x040 NextProcessor : UChar<br>+0x041 DeferredProcessor : UChar<br>+0x042 AdjustReason : UChar<br>+0x043 AdjustIncrement : Char<br>+0x044 ApcQueueLock : Uint4B<br>+0x048 ContextSwitches : Uint4B<br>+0x04c State : UChar<br>+0x04d NpxState : UChar<br>+0x04e WaitIrql : UChar<br>+0x04f WaitMode : Char<br>+0x050 WaitStatus : Int4B<br>+0x054 WaitBlockList : Ptr32 _KWAIT_BLOCK<br>+0x054 GateObject : Ptr32 _KGATE<br>+0x058 Alertable : UChar<br>+0x059 WaitNext : UChar<br>+0x05a WaitReason : UChar<br>+0x05b Priority : Char<br>+0x05c EnableStackSwap : UChar<br>+0x05d SwapBusy : UChar<br>+0x05e Alerted : [2] UChar<br>+0x060 WaitListEntry : _LIST_ENTRY<br>+0x060 SwapListEntry : _SINGLE_LIST_ENTRY<br>+0x068 Queue : Ptr32 _KQUEUE<br>+0x06c WaitTime : Uint4B<br>+0x070 KernelApcDisable : Int2B<br>+0x072 SpecialApcDisable : Int2B<br>+0x070 CombinedApcDisable : Uint4B<br>+0x074 Teb : Ptr32 Void<br>+0x078 Timer : _KTIMER<br>+0x078 TimerFill : [40] UChar<br>+0x0a0 AutoAlignment : Pos 0, 1 Bit<br>+0x0a0 DisableBoost : Pos 1, 1 Bit<br>+0x0a0 ReservedFlags : Pos 2, 30 Bits<br>+0x0a0 ThreadFlags : Int4B<br>+0x0a8 WaitBlock : [4] _KWAIT_BLOCK<br>+0x0a8 WaitBlockFill0 : [23] UChar<br>+0x0bf SystemAffinityActive : UChar<br>+0x0a8 WaitBlockFill1 : [47] UChar<br>+0x0d7 PreviousMode : Char<br>+0x0a8 WaitBlockFill2 : [71] UChar<br>+0x0ef ResourceIndex : UChar<br>+0x0a8 WaitBlockFill3 : [95] UChar<br>+0x107 LargeStack : UChar<br>+0x108 QueueListEntry : _LIST_ENTRY<br>+0x110 TrapFrame : Ptr32 _KTRAP_FRAME<br>+0x114 CallbackStack : Ptr32 Void<br>+0x118 ServiceTable : Ptr32 Void<br>+0x11c ApcStateIndex : UChar<br>+0x11d IdealProcessor : UChar<br>+0x11e Preempted : UChar<br>+0x11f ProcessReadyQueue : UChar<br>+0x120 KernelStackResident : UChar<br>+0x121 BasePriority : Char<br>+0x122 PriorityDecrement : Char<br>+0x123 Saturation : Char<br>+0x124 UserAffinity : Uint4B<br>+0x128 Process : Ptr32 _KPROCESS<br>+0x12c Affinity : Uint4B<br>+0x130 ApcStatePointer : [2] Ptr32 _KAPC_STATE<br>+0x138 SavedApcState : _KAPC_STATE<br>+0x138 SavedApcStateFill : [23] UChar<br>+0x14f FreezeCount : Char<br>+0x150 SuspendCount : Char<br>+0x151 UserIdealProcessor : UChar<br>+0x152 CalloutActive : UChar<br>+0x153 Iopl : UChar<br>+0x154 Win32Thread : Ptr32 Void<br>+0x158 StackBase : Ptr32 Void<br>+0x15c SuspendApc : _KAPC<br>+0x15c SuspendApcFill0 : [1] UChar<br>&#8230;&#8230;&#8230;&#8230;</p>
<p>&#8230;&#8230;&#8230;&#8230;</p>
<p>上面红色部分是APC机制用到的几个字段！！</p>
<p>kd&gt; dt _KAPC_STATE<br>nt!_KAPC_STATE<br>+0x000 ApcListHead : [2] _LIST_ENTRY，每个指针指向_KAPC结构<br>+0x010 Process : Ptr32 _KPROCESS<br>+0x014 KernelApcInProgress : UChar<br>+0x015 KernelApcPending : UChar<br>+0x016 UserApcPending : UChar</p>
<p>显然，这里的 ApcListHead 就是 APC 队列头。不过这是个大小为 2 的数组，说明实际<br>上(每个线程)有两个 APC 队列。这是因为 APC 函数分为用户 APC 和内核 APC 两种，各有<br>各的队列。所谓用户 APC，是指相应的 APC 函数位于用户空间、在用户空间执行；而内核<br>APC，则相应的 APC 函数为内核函数。</p>
<p>SavedApcState也是个_KAPC_STATE结构，当当前程暂时&#8220;挂靠(Attach)&#8221;到另一个进程的地址空间的时侯，ApcState就拷贝到SavedApcState暂时存放！</p>
<p>当然，还要有状态信息说明本线程当前是处于&#8220;原始环境&#8221;还是&#8220;挂靠环境&#8221;，这就是 ApcStateIndex 的作用，代码中为 ApcStateIndex的值定义了一种枚举类型：</p>
<p>typedef enum _KAPC_ENVIRONMENT {<br>OriginalApcEnvironment,<br>AttachedApcEnvironment,<br>CurrentApcEnvironment,<br>InsertApcEnvironment<br>} KAPC_ENVIRONMENT;</p>
<p>实际可用于 ApcStateIndex 的只是 OriginalApcEnvironment和 AttachedApcEnvironment。</p>
<p>KAPC_STATE 指针数组 ApcStatePointer[2]，就用ApcStateIndex 的当前值作为下标，而数组中的指针则根据情况可以分别指向两个APC_STATE 数据结构中的一个。</p>
<p>kd&gt; dt _KAPC ;APC对象<br>nt!_KAPC<br>+0x000 Type : UChar<br>+0x001 SpareByte0 : UChar<br>+0x002 Size : UChar<br>+0x003 SpareByte1 : UChar<br>+0x004 SpareLong0 : Uint4B<br>+0x008 Thread : Ptr32 _KTHREAD<br>+0x00c ApcListEntry : _LIST_ENTRY<br>+0x014 KernelRoutine : Ptr32 void<br>+0x018 RundownRoutine : Ptr32 void<br>+0x01c NormalRoutine : Ptr32 void<br>+0x020 NormalContext : Ptr32 Void<br>+0x024 SystemArgument1 : Ptr32 Void<br>+0x028 SystemArgument2 : Ptr32 Void<br>+0x02c ApcStateIndex : Char<br>+0x02d ApcMode : Char<br>+0x02e Inserted : UChar</p>
<p>KernelRoutine、RundownRoutine、NormalRoutine。其中只有 NormalRoutine才指向(执行)APC 函数的请求者所提供的函数，其余两个都是辅助性的！</p>
<p>NTKERNELAPI<br>VOID<br>KeInitializeApc (<br>__out PRKAPC Apc,<br>__in PRKTHREAD Thread,<br>__in KAPC_ENVIRONMENT Environment,<br>__in PKKERNEL_ROUTINE KernelRoutine,<br>__in_opt PKRUNDOWN_ROUTINE RundownRoutine,<br>__in_opt PKNORMAL_ROUTINE NormalRoutine,<br>__in_opt KPROCESSOR_MODE ProcessorMode,<br>__in_opt PVOID NormalContext<br>);</p>
<p>这个函数主要用来初始化Apc（_KAPC）这个结构的，如果Environment==CurrentApcEnvironment,Apc-&gt;ApcStateIndex就由KTHREAD中的ApcStateIndex决定，但Environment不能大于KTHREAD中的ApcStateIndex！</p>
<p>最后，APC 请求的模式ProcessorMode，但是有个例外，那就是：如果指针NormalRoutine 为 0，那么实际的模式变成了 KernelMode。这是因为在这种情况下没有用户空间APC函数可以执行， 唯一将得到执行的是KernelRoutine！</p>
<p>最后，KeInitializeApc 设置Inserted域为FALSE。然而初始化APC对象，并没有把它存放到相应的APC队列中。</p>
<p><br>NTKERNELAPI<br>BOOLEAN<br>KeInsertQueueApc (<br>__inout PRKAPC Apc,<br>__in_opt PVOID SystemArgument1,<br>__in_opt PVOID SystemArgument2,<br>__in KPRIORITY Increment<br>);</p>
<p>据APC请求的具体情况，有时候要插在队列的前头，一般则挂在队列的尾部。</p>
<p>_KiServiceExit:</p>
<p>cli ; disable interrupts<br>DISPATCH_USER_APC ebp, ReturnCurrentEax</p>
<p>;<br>; Exit from SystemService<br>;</p>
<p>EXIT_ALL NoRestoreSegs, NoRestoreVolatile ;这个宏以后再讲</p>
<p>DISPATCH_USER_APC macro TFrame, ReturnCurrentEax<br>local a, b, c<br>c:<br>.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)</p>
<p>test byte ptr [TFrame]+TsEflags+2, EFLAGS_V86_MASK/010000h ; is previous mode v86?<br>jnz short b ; if nz, yes, go check for APC<br>test byte ptr [TFrame]+TsSegCs,MODE_MASK ; is previous mode user mode?<br>jz a ; No, previousmode=Kernel, jump out<br>b: mov ebx, PCR[PcPrcbData+PbCurrentThread]; get addr of current thread<br>mov byte ptr [ebx]+ThAlerted, 0 ; clear kernel mode alerted<br>cmp byte ptr [ebx]+ThApcState.AsUserApcPending, 0<br>je a ; if eq, no user APC pending</p>
<p>mov ebx, TFrame<br>ifnb &lt;ReturnCurrentEax&gt;;条件宏汇编，如果ReturnCurrentEax参数不为空，则编译！</p>
<p>;DISPATCH_USER_APC ebp, ReturnCurrentEax，显然这里是编译的！<br>mov [ebx].TsEax, eax ; Store return code in trap frame<br>mov dword ptr [ebx]+TsSegFs, KGDT_R3_TEB OR RPL_MASK<br>mov dword ptr [ebx]+TsSegDs, KGDT_R3_DATA OR RPL_MASK<br>mov dword ptr [ebx]+TsSegEs, KGDT_R3_DATA OR RPL_MASK<br>mov dword ptr [ebx]+TsSegGs, 0<br>endif</p>
<p>;<br>; Save previous IRQL and set new priority level<br>;<br>RaiseIrql APC_LEVEL<br>push eax ; Save OldIrql</p>
<p>sti ; Allow higher priority ints</p>
<p>;<br>; call the APC delivery routine.<br>;<br>; ebx - Trap frame<br>; 0 - Null exception frame<br>; 1 - Previous mode<br>;<br>; call APC deliver routine<br>;</p>
<p>stdCall _KiDeliverApc, &lt;1, 0, ebx&gt; ;1就是UserMode</p>
<p>pop ecx ; (ecx) = OldIrql<br>LowerIrql ecx</p>
<p>ifnb &lt;ReturnCurrentEax&gt; ;同上分析<br>mov eax, [ebx].TsEax ; Restore eax, just in case<br>endif</p>
<p>cli<br>jmp b ; 注意这个循环！！</p>
<p>ALIGN 4<br>a:<br>endm</p>
<p>这段代码主要检查：</p>
<p>即将返回的是否用户空间。<br>是否有用户APC请求正在等待执行</p>
<p>条件符合才用KiDeliverApc真正投递APC。注意代码jmp b，好像在返回用户空间前KiDeliverApc会被循环调用直到没有user APC，其实不是的，KiDeliverApc每处理完一个User APC就把UserApcPending清零，所以User APCs在返回用户空间时还是只能投递一次！KiDeliverApc中用while处理完所有Kernel Mode APCs，但User Mode APC却只处理一个！事实上User APC的投递是很特殊的，以后会讲到，而且每次只能投递一次！</p>
<p><br>前面讲过，KTHREAD 中有两个 KAPC_STATE 数据结构，一个是 ApcState，另一个是SavedApcState，二者都有APC 队列，但是要投递的只是ApcState 中的队列。</p>
<p>KiDeliverApc (<br>IN KPROCESSOR_MODE PreviousMode,//写成DeliverMode不是更好<br>IN PKEXCEPTION_FRAME ExceptionFrame,//这个参数几乎就是0<br>IN PKTRAP_FRAME TrapFrame<br>)</p>
<p>这个函数里面还比较复杂，代码不帖了。</p>
<p>参数PreviousMode表示需要&#8220;投递&#8221;哪一种 APC，可以是UserMode，也可以是KernelMode。不过，KernelMode 确实表示只要求执行内核 APC，而UserMode 却表示在执行内核 APC 之外再执行用户APC。</p>
<p>The Windows operating system uses three kinds of APCs:</p>
<p><br>User APCs run strictly in user mode and only when the current thread is in an alertable wait state. The operating system uses user APCs to implement mechanisms such as overlapped I/O and the QueueUserApc Win32 routine. （run IRQL = PASSIVE_LEVEL）<br>Normal kernel APCs run in kernel mode at IRQL = PASSIVE_LEVEL. A normal kernel APC preempts all user-mode code, including user APCs. Normal kernel APCs are generally used by file systems and file-system filter drivers.<br>Special kernel APCs run in kernel mode at IRQL = APC_LEVEL. A special kernel APC preempts user-mode code and kernel-mode code that executes at IRQL = PASSIVE_LEVEL, including both user APCs and normal kernel APCs. The operating system uses special kernel APCs to handle operations such as I/O request completion.<br>从代码的角度看是这样的：</p>
<p>User APCs<br>_KAPC.ApcMode==UserMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine!=NULL</p>
<p>Normal kernel APCs</p>
<p>_KAPC.ApcMode==KernelMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine!=NULL</p>
<p>Special kernel APCs</p>
<p>_KAPC.ApcMode==KernelMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine==NULL</p>
<p>有一点它们的_KAPC.KernelRoutine肯定不为空。并且，如果NormolRoutine也不为空，那么KernelRoutine都在NormolRoutine被调用前被调用！！</p>
<p>上文中讲到投递User Mode APCs是很特殊的，道理很简单，因为User Mode APC是ring3下的回调函数，显然ring0中的KiDeliverAPC（）不能像Kernel Mode APC那样直接call，必须要先回到ring3环境下。当然不能像普通情况那样返回（否则就回到ring3系统调用的地方了），只有一个办法，那就是修改TrapFrame ，欺骗系统返回&#8220;APC回调函数&#8221;！KiInitializeUserApc就是这么做的！</p>
<p>该函数首先把TrapFrame转化为ContextFrame，然后移动用户态ESP指针，分配大约sizeof（CONTEXT）+sizeof（KAPC_RECORD）个字节:</p>
<p>UserStack-&gt; &#8230;&#8230;</p>
<p>KAPC_RECORD</p>
<p>&#8230;&#8230;</p>
<p>&#8230;&#8230;</p>
<p>CONTEXT</p>
<p>TopOfStack-&gt; &#8230;&#8230;</p>
<p>KAPC_RECORD就是KiInitializeUserApc的最后四个参数</p>
<p>nt!_KAPC_RECORD</p>
<p>+0x000 NormalRoutine : Ptr32 void</p>
<p>+0x004 NormalContext : Ptr32 Void</p>
<p>+0x008 SystemArgument1 : Ptr32 Void</p>
<p>+0x00c SystemArgument2 : Ptr32 Void</p>
<p>CONTEXT结构主要来存放被修改前的TrapFrame，之所以用CONTEXT结构是跟APC函数返回有关！</p>
<p>TrapFrame-&gt;HardwareEsp = UserStack;</p>
<p>TrapFrame-&gt;Eip = (ULONG)KeUserApcDispatcher;</p>
<p>TrapFrame-&gt;ErrCode = 0;</p>
<p>从上面的代码看到确实修改了TrapFrame，并且返回到的是ring3下的KeUserApcDispatcher，刚才说的_KAPC_RECORD其实也是它的参数！真正我们的User APC回调函数是由KeUserApcDispatcher调用的！</p>
<p>.func <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#75;&#105;&#85;&#115;&#101;&#114;&#65;&#112;&#99;&#68;&#105;&#115;&#112;&#97;&#116;&#99;&#104;&#101;&#114;&#64;&#49;&#54;">KiUserApcDispatcher@16</a></p>
<p>.globl <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#75;&#105;&#85;&#115;&#101;&#114;&#65;&#112;&#99;&#68;&#105;&#115;&#112;&#97;&#116;&#99;&#104;&#101;&#114;&#64;&#49;&#54;">_KiUserApcDispatcher@16</a></p>
<p><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#75;&#105;&#85;&#115;&#101;&#114;&#65;&#112;&#99;&#68;&#105;&#115;&#112;&#97;&#116;&#99;&#104;&#101;&#114;&#64;&#49;&#54;">_KiUserApcDispatcher@16</a>:</p>
<p>/* Setup SEH stack */</p>
<p>lea eax, [esp+CONTEXT_ALIGNED_SIZE+16]</p>
<p>mov ecx, fs:[TEB_EXCEPTION_LIST]</p>
<p>mov edx, offset _KiUserApcExceptionHandler</p>
<p>mov [eax], ecx</p>
<p>mov [eax+4], edx</p>
<p>/* Enable SEH */</p>
<p>mov fs:[TEB_EXCEPTION_LIST], eax</p>
<p>/* Put the Context in EDI */</p>
<p>pop eax</p>
<p>lea edi, [esp+12]</p>
<p>/* Call the APC Routine */</p>
<p>call eax</p>
<p>/* Restore exception list */</p>
<p>mov ecx, [edi+CONTEXT_ALIGNED_SIZE]</p>
<p>mov fs:[TEB_EXCEPTION_LIST], ecx</p>
<p>/* Switch back to the context */</p>
<p>push 1 ; TestAlert</p>
<p>push edi ;edi-&gt;CONTEXT结构</p>
<p>call <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#90;&#119;&#67;&#111;&#110;&#116;&#105;&#110;&#117;&#101;&#64;&#56;">_ZwContinue@8</a></p>
<p>;;不会返回到这里的</p>
<p>上面的代码并不难理解，我们的User APC回调函数返回后，立即调用了ZwContinue，这是个ntdll中的导出函数，这个函数又通过系统调用进入kernel中的NtContinue！</p>
<p>NTSTATUS</p>
<p>; NtContinue (</p>
<p>; IN PCONTEXT ContextRecord,</p>
<p>; IN BOOLEAN TestAlert</p>
<p>; )</p>
<p>;</p>
<p>; Routine Description:</p>
<p>;</p>
<p>; This routine is called as a system service to continue execution after</p>
<p>; an exception has occurred. Its function is to transfer information from</p>
<p>; the specified context record into the trap frame that was built when the</p>
<p>; system service was executed, and then exit the system as if an exception</p>
<p>; had occurred.</p>
<p>;</p>
<p>; WARNING - Do not call this routine directly, always call it as</p>
<p>; ZwContinue!!! This is required because it needs the</p>
<p>; trapframe built by KiSystemService.</p>
<p>;</p>
<p>; Arguments:</p>
<p>;</p>
<p>; KTrapFrame (ebp+0: after setup) -&gt; base of KTrapFrame</p>
<p>;</p>
<p>; ContextRecord (ebp+8: after setup) = Supplies a pointer to a context rec.</p>
<p>;</p>
<p>; TestAlert (esp+12: after setup) = Supplies a boolean value that specifies</p>
<p>; whether alert should be tested for the previous processor mode.</p>
<p>;</p>
<p>; Return Value:</p>
<p>;</p>
<p>; Normally there is no return from this routine. However, if the specified</p>
<p>; context record is misaligned or is not accessible, then the appropriate</p>
<p>; status code is returned.</p>
<p>;</p>
<p>;--</p>
<p>NcTrapFrame equ [ebp + 0]</p>
<p>NcContextRecord equ [ebp + 8]</p>
<p>NcTestAlert equ [ebp + 12]</p>
<p>align dword</p>
<p>cPublicProc _NtContinue ,2</p>
<p>push ebp ;ebp-&gt;TrapFrame</p>
<p>;</p>
<p>; Restore old trap frame address since this service exits directly rather</p>
<p>; than returning.</p>
<p>;</p>
<p>mov ebx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address</p>
<p>mov edx, [ebp].TsEdx ; restore old trap frame address</p>
<p>mov [ebx].ThTrapFrame, edx ;</p>
<p>;</p>
<p>; Call KiContinue to load ContextRecord into TrapFrame. On x86 TrapFrame</p>
<p>; is an atomic entity, so we don't need to allocate any other space here.</p>
<p>;</p>
<p>; KiContinue(NcContextRecord, 0, NcTrapFrame)</p>
<p>;</p>
<p>mov ebp,esp</p>
<p>mov eax, NcTrapFrame</p>
<p>mov ecx, NcContextRecord</p>
<p>stdCall _KiContinue, &lt;ecx, 0, eax&gt;</p>
<p>or eax,eax ; return value 0?</p>
<p>jnz short Nc20 ; KiContinue failed, go report error</p>
<p>;</p>
<p>; Check to determine if alert should be tested for the previous processor mode.</p>
<p>;</p>
<p>cmp byte ptr NcTestAlert,0 ; Check test alert flag</p>
<p>je short Nc10 ; if z, don't test alert, go Nc10</p>
<p>mov al,byte ptr [ebx]+ThPreviousMode ; No need to xor eax, eax.</p>
<p>stdCall _KeTestAlertThread, &lt;eax&gt; ; test alert for current thread</p>
<p>;如果User APCs不为空，它会设置UserApcPending,</p>
<p>;跟Alertable无关</p>
<p>Nc10: pop ebp ; (ebp) -&gt; TrapFrame</p>
<p>mov esp,ebp ; (esp) = (ebp) -&gt; trapframe</p>
<p>jmp _KiServiceExit2 ; common exit</p>
<p>Nc20: pop ebp ; (ebp) -&gt; TrapFrame</p>
<p>mov esp,ebp ; (esp) = (ebp) -&gt; trapframe</p>
<p>jmp _KiServiceExit ; common exit</p>
<p>stdENDP _NtContinue</p>
<p>NtContinue把CONTEXT结构转化成TrapFrame（回复原来的陷阱帧），然后就从KiServiceExit2处退出系统调用！</p>
<p>;++</p>
<p>;</p>
<p>; _KiServiceExit2 - same as _KiServiceExit BUT the full trap_frame</p>
<p>; context is restored</p>
<p>;</p>
<p>;--</p>
<p>public _KiServiceExit2</p>
<p>_KiServiceExit2:</p>
<p>cli ; disable interrupts</p>
<p>DISPATCH_USER_APC ebp</p>
<p>;</p>
<p>; Exit from SystemService</p>
<p>;</p>
<p>EXIT_ALL ; RestoreAll</p>
<p>KiServiceExit2跟KiServiceExit差不多，只是宏参数的不同!同样如果还有User APC又会进入上文描述的情形，直到没有User APC，至此才会返回真正发起原始系统调用的地方！</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/better0332/archive/2009/06/29/4306683.aspx">http://blog.csdn.net/better0332/archive/2009/06/29/4306683.aspx</a></p><img src ="http://www.cppblog.com/shaker/aggbug/145733.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/shaker/" target="_blank">shaker(太子)</a> 2011-05-05 12:09 <a href="http://www.cppblog.com/shaker/archive/2011/05/05/145733.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[zt]谈谈对APC的一点理解</title><link>http://www.cppblog.com/shaker/archive/2011/05/05/145731.html</link><dc:creator>shaker(太子)</dc:creator><author>shaker(太子)</author><pubDate>Thu, 05 May 2011 03:46:00 GMT</pubDate><guid>http://www.cppblog.com/shaker/archive/2011/05/05/145731.html</guid><wfw:comment>http://www.cppblog.com/shaker/comments/145731.html</wfw:comment><comments>http://www.cppblog.com/shaker/archive/2011/05/05/145731.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/shaker/comments/commentRss/145731.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/shaker/services/trackbacks/145731.html</trackback:ping><description><![CDATA[<span style="WIDOWS: 2; TEXT-TRANSFORM: none; TEXT-INDENT: 0px; LETTER-SPACING: normal; BORDER-COLLAPSE: separate; FONT: 16px Arial,Microsoft Yahei,Simsun,sans-serif; WHITE-SPACE: normal; ORPHANS: 2; COLOR: rgb(83,100,130); WORD-SPACING: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px" class=Apple-style-span><span style="LINE-HEIGHT: 18px; FONT-FAMILY: 'Lucida Grande', 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif; COLOR: rgb(51,51,51); FONT-SIZE: 13px" class=Apple-style-span>异步过程调用(APCs) 是NT异步处理体系结构中的一个基础部分，理解了它，对于了解NT怎样操作和执行几个核心的系统操作很有帮助。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">1) APCs允许用户程序和系统元件在一个进程的地址空间内某个线程的上下文中执行代码。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">2) I/O管理器使用APCs来完成一个线程发起的异步的I/O操作。例如：当一个设备驱动调用IoCompleteRequest来通知I/O管理器，它已经结束处理一个异步I/O请求时，I/O管理器排队一个apc到发起请求的线程。然后线程在一个较低IRQL级别，来执行APC. APC的作用是从系统空间拷贝I/O操作结果和状态信息到线程虚拟内存空间的一个缓冲中。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">3) 使用APC可以得到或者设置一个线程的上下文和挂起线程的执行。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">尽管APCs在nt体系结构下被广泛使用，但是关于怎样使用它的文档却非常的缺乏。本篇我们详细介绍下nt系统是怎样处理APCs的，并且记录导出的nt函数，方便设备驱动开发者在他们的程序中使用APCs。我也会展示一个非常可靠的NT的APC调度子程序KiDeliverApc的实现，来帮助你更好的掌握APC调度的内幕。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">APC对象<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">在NT中，有两种类型的APCs：用户模式和内核模式。用户APCs运行在用户模式下目标线程当前上下文中，并且需要从目标线程得到许可来运行。特别是，用户模式的APCs需要目标线程处在alertable等待状态才能被成功的调度执行。通过调用下面任意一个函数，都可以让线程进入这种状态。这些函数是：KeWaitForSingleObject, KeWaitForMultipleObjects, KeWaitForMutexObject, KeDelayExecutionThread。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">对于用户模式下，可以调用函数SleepEx, SignalObjectAndWait, WaitForSingleObjectEx, WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx 都可以使目标线程处于alertable等待状态，从而让用户模式APCs执行,原因是这些函数最终都是调用了内核中的KeWaitForSingleObject, KeWaitForMultipleObjects, KeWaitForMutexObject, KeDelayExecutionThread等函数。另外通过调用一个未公开的alert-test服务KeTestAlertThread，用户线程可以使用户模式APCs执行。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">当一个用户模式APC被投递到一个线程，调用上面的等待函数，如果返回等待状态STATUS_USER_APC，在返回用户模式时，内核转去控制APC例程，当APC例程完成后，再继续线程的执行.<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">和用户模式APCs比较，内核模式APCs执行在内核模式下。可以被划分为常规的和特殊的两类。当APCs被投递到一个特殊的线程，特殊的内核模式APCs不需要从线程得到许可来运行。然而，常规的内核模式APCs在他们成功执行前，需要有特定的环境。此外，特殊的内核APC被尽可能快地执行，既只要APC_LEVEL级上有可调度的活动。在很多情况下，特殊的内核APC甚至能唤醒阻塞的线程。普通的内核APC仅在所有特殊APC都被执行完，并且目标线程仍在运行，同时该线程中也没有其它内核模式APC正执行时才执行。用户模式APC在所有内核模式APC执行完后才执行，并且仅在目标线程有alertable属性时才执行。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">每一个等待执行的APC都存在于一个线程执行体，由内核管理的队列中。系统中的每一个线程都包含两个APC队列，一个是为用户模式APCs,另一个是为内核模式APCs的。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NT通过一个成为KAPC的内核控制对象来描述一个ＡＰＣ．尽管ＤＤＫ中没有明确的文档化ＡＰＣｓ，但是在NTDDK.H中却非常清楚的定义了ＡＰＣ对象。从下面的KAPC对象的定义看，有些是不需要说明的。像Type和Size。Type表示了这是一个APC内核对象。在nt中，每一个内核对象或者执行体对象都有Type和Size这两个域。由此处理函数可以确定当前处理的对象。Size表示一个字对齐的结构体的大小。也就是指明了对象占的内存空间大小。Spare0看起来有些晦涩难懂，但是它是没用什么任何深远的意义，仅仅是为了内存补齐。其他的域将在下面的篇幅中介绍。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">//-------------------------------------------------------------------------------------------------------<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">几个函数声明和结构定义：<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">typedef struct _KAPC {<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">CSHORT Type;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">CSHORT Size;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">ULONG Spare0;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">struct _KTHREAD *Thread;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">LIST_ENTRY ApcListEntry;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">PKKERNEL_ROUTINE KernelRoutine;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">PKRUNDOWN_ROUTINE RundownRoutine;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">PKNORMAL_ROUTINE NormalRoutine;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">PVOID NormalContext;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">//<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">// N.B. The following two members MUST be together.<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">//<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">PVOID SystemArgument1;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">PVOID SystemArgument2;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">CCHAR ApcStateIndex;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">KPROCESSOR_MODE ApcMode;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">BOOLEAN Inserted;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">} KAPC, *PKAPC, *RESTRICTED_POINTER PRKAPC;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">//------<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">APC环境<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">一个线程在它执行的任意时刻，假设当前的IRQL是在Passive级，它可能需要临时在其他的进程上下文中执行代码，为了完成这个操作，线程调用系统功能函数KeAttachProcess，在从这个调用返回时，线程执行在另一个进程的地址空间。先前所有在线程自己的进程上下文中等待执行的APCs,由于这时其所属进程的地址空间不是当前可用的，因此他们不能被投递执行。然而，新的插入到这个线程的APCs可以执行在这个新的进程空间。甚至当线程最后从新的进程中分离时，新的插入到这个线程的APCs还可以在这个线程所属的进程上下文中执行。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">为了达到控制APC传送的这个程度，NT中每个线程维护了两个APC环境或者说是状态。每一个APC环境包含了用户模式的APC队列和内核模式的APC队列，一个指向当前进程对象的指针和三个控制变量，用于指出：是否有未决的内核模式APCs(KernelApcPending),是否有常规内核模式APC在进行中(KernelApcInProgress)，是否有未决的用户模式的APC(UserApcPending). 这些APC的环境保存在线程对象的ApcStatePointer域中。这个域是由2个元素组成的数组。即：+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">typedef struct _KAPC_STATE {<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">LIST_ENTRY ApcListHead[MaximumMode];<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">struct _KPROCESS *Process;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">BOOLEAN KernelApcInProgress;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">BOOLEAN KernelApcPending;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">BOOLEAN UserApcPending;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">} KAPC_STATE, *PKAPC_STATE, *PRKAPC_STATE;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">lkd&gt; dt _kthread<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">ntdll!_KTHREAD<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x000 Header : _DISPATCHER_HEADER<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x010 MutantListHead : _LIST_ENTRY<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x018 InitialStack : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x01c StackLimit : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x020 Teb : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x024 TlsArray : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x028 KernelStack : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x02c DebugActive : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x02d State : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x02e Alerted : [2] UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x030 Iopl : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x031 NpxState : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x032 Saturation : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x033 Priority : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x034 ApcState : _KAPC_STATE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x04c ContextSwitches : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x050 IdleSwapBlock : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x051 Spare0 : [3] UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x054 WaitStatus : Int4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x058 WaitIrql : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x059 WaitMode : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x05a WaitNext : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x05b WaitReason : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x05c WaitBlockList : Ptr32 _KWAIT_BLOCK<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x060 WaitListEntry : _LIST_ENTRY<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x060 SwapListEntry : _SINGLE_LIST_ENTRY<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x068 WaitTime : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x06c BasePriority : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x06d DecrementCount : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x06e PriorityDecrement : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x06f Quantum : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x070 WaitBlock : [4] _KWAIT_BLOCK<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0d0 LegoData : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0d4 KernelApcDisable : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0d8 UserAffinity : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0dc SystemAffinityActive : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0dd PowerState : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0de NpxIrql : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0df InitialNode : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0e0 ServiceTable : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0e4 Queue : Ptr32 _KQUEUE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0e8 ApcQueueLock : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x0f0 Timer : _KTIMER<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x118 QueueListEntry : _LIST_ENTRY<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x120 SoftAffinity : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x124 Affinity : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x128 Preempted : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x129 ProcessReadyQueue : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x12a KernelStackResident : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x12b NextProcessor : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x12c CallbackStack : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x130 Win32Thread : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x134 TrapFrame : Ptr32 _KTRAP_FRAME<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x140 PreviousMode : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x141 EnableStackSwap : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x142 LargeStack : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x143 ResourceIndex : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x144 KernelTime : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x148 UserTime : Uint4B<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x14c SavedApcState : _KAPC_STATE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x164 Alertable : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x165 ApcStateIndex : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x166 ApcQueueable : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x167 AutoAlignment : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x168 StackBase : Ptr32 Void<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x16c SuspendApc : _KAPC<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x19c SuspendSemaphore : _KSEMAPHORE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x1b0 ThreadListEntry : _LIST_ENTRY<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x1b8 FreezeCount : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x1b9 SuspendCount : Char<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x1ba IdealProcessor : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x1bb DisableBoost : UChar<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">主APC环境是位于线程对象的ApcState 域，即：<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">+0x034 ApcState : _KAPC_STATE<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">线程中等待在当前进程上下文中执行的APC保存在ApcState的队列中。无论何时，NT的APC派发器(dispatcher)和其他系统元件查询一个线程未决的APCs时, 他们都会检查主APC环境，如果这里有任何未决的APCs,就会马上被投递，或者修改它的控制变量稍后投递。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">第二个APC环境是位于线程对象的SavedApcState域，当线程临时挂接到其他进程时，它是用来备份主APC环境的。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">当一个线程调用KeAttachProcess，在另外的进程上下文中执行后续的代码时，ApcState域的内容就被拷贝到SavedApcState域。然后ApcState域被清空，它的APC队列重新初始化，控制变量设置为0，当前进程域设置为新的进程。这些步骤成功的确保先前在线程所属的进程上下文地址空间中等待的APCs，当线程运行在其它不同的进程上下文时，这些APCs不被传送执行。随后，ApcStatePointer域数组内容被更新来反映新的状态，数组中第一个元素指向SavedApcState域，第二个元素指向ApcState域，表明线程所属进程上下文的APC环境位于SavedApcState域。线程的新的进程上下文的APC环境位于ApcState域。最后，当前进程上下文切换到新的进程上下文。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">对于一个APC对象，决定当前APC环境的是ApcStateIndex域。ApcStateIndex域的值作为ApcStatePointer域数组的索引来得到目标APC环境指针。随后，目标APC环境指针用来在相应的队列中存放apc对象.<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">当线程从新的进程中脱离时(KeDetachProcess), 任何在新的进程地址空间中等待执行的未决的内核APCs被派发执行。随后SavedApcState 域的内容被拷贝回ApcState域。SavedApcState 域的内容被清空，线程的ApcStateIndex域被设为OriginalApcEnvironment，ApcStatePointer域更新，当前进程上下文切换到线程所属进程。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">使用APCs<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">设备驱动程序使用两个主要函数来利用APCs, 第一个是KeInitializeApc，用来初始化APC对象。这个函数接受一个驱动分配的APC对象，一个目标线程对象指针，APC环境索引（指出APC对象存放于哪个APC环境），APC的kernel,rundown和normal例程指针，APC类型（用户模式或者内核模式）和一个上下文参数。 函数声明如下：<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NTKERNELAPI<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">VOID<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">KeInitializeApc (<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PRKAPC Apc,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PKTHREAD Thread,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN KAPC_ENVIRONMENT Environment,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PKKERNEL_ROUTINE KernelRoutine,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN KPROCESSOR_MODE ApcMode,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID NormalContext<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">);<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">typedef enum _KAPC_ENVIRONMENT {<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">OriginalApcEnvironment,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">AttachedApcEnvironment,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">CurrentApcEnvironment<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">} KAPC_ENVIRONMENT;<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">KeInitializeApc 首先设置APC对象的Type和Size域一个适当的值，然后检查参数Environment的值，如果是CurrentApcEnvironment，那么ApcStateIndex域设置为目标线程的ApcStateIndex域。否则，ApcStateIndex域设置为参数Environment的值。随后，函数直接用参数设置APC对象Thread，RundownRoutine，KernelRoutine域的值。为了正确地确定APC的类型，KeInitializeApc 检查参数NormalRoutine的值，如果是NULL，ApcMode域的值设置为KernelMode，NormalContext域设置为NULL。如果NormalRoutine的值不是NULL，这时候它一定指向一个有效的例程，就用相应的参数来设置ApcMode域和NormalContext域。最后，KeInitializeApc 设置Inserted域为FALSE.然而初始化APC对象，并没有把它存放到相应的APC队列中。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">从这个解释看，你可以了解到APCs对象如果缺少有效的NormalRoutine，就会被当作内核模式APCs.尤其是它们会被认为是特殊的内核模式APCs.<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">实际上，I/O管理器就是用这类的APC来完成异步I/O操作。相反地，APC对象定义了有效的NormalRoutine，并且ApcMode域是KernelMode，就会被当作常规的内核模式APCs,否则就会被当作是用户模式APCs. NTDDK.H中KernelRoutine, RundownRoutine, and NormalRoutine 的定义如下：<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">typedef<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">VOID<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">(*PKKERNEL_ROUTINE) (<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN struct _KAPC *Apc,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN OUT PKNORMAL_ROUTINE *NormalRoutine,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN OUT PVOID *NormalContext,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN OUT PVOID *SystemArgument1,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN OUT PVOID *SystemArgument2<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">);<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">typedef<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">VOID<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">(*PKRUNDOWN_ROUTINE) (<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN struct _KAPC *Apc<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">);<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">typedef<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">VOID<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">(*PKNORMAL_ROUTINE) (<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID NormalContext,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID SystemArgument1,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID SystemArgument2<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">);<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">//------------------<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">通常，无论是什么类型，每个APC对象必须要包含一个有效的KernelRoutine 函数指针。当这个APC被NT的APC dispatcher传送执行时，这个例程首先被执行。用户模式的APCs必须包含一个有效的NormalRoutine 函数指针，这个函数必须在用户内存区域。同样的，常规内核模式APCs也必须包含一个有效的NormalRoutine，但是它就像KernelRoutine一样运行在内核模式。作为可选择的，任意类型的APC都可以定义一个有效的RundownRoutine，这个例程必须在内核内存区域，并且仅仅当系统需要释放APC队列的内容时，才被调用。例如线程退出时，在这种情况下，KernelRoutine和NormalRoutine都不执行，只有RundownRoutine执行。没有这个例程的APC对象会被删除。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">记住，投递APCs到一个线程的动作，仅仅是操作系统调用KiDeliverApc完成的。执行APC实际上就是调用APC内的例程。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">一旦APC对象完成初始化后，设备驱动调用KeInsertQueueApc来将APC对象存放到目标线程的相应的APC队列中。这个函数接受一个由KeInitializeApc完成初始化的APC对象指针，两个系统参数和一个优先级增量。跟传递给KeInitializeApc函数的参数context 一样，这两个系统参数只是在APC的例程执行时，简单的传递给APC的例程。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NTKERNELAPI<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">BOOLEAN<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">KeInsertQueueApc (<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PRKAPC Apc,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID SystemArgument1,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID SystemArgument2,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN KPRIORITY Increment<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">);<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">//-----------------<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">在KeInsertQueueApc 将APC对象存放到目标线程相应的APC队列之前，它首先检查目标线程是否是APC queueable。如果不是，函数立即返回FALSE.如果是，函数直接用参数设置SystemArgument1域和SystemArgument2 域，随后，函数调用KiInsertQueueApc来将APC对象存放到相应的APC队列。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">KiInsertQueueApc 仅仅接受一个APC对象和一个优先级增量。这个函数首先得到线程APC队列的spinlock并且持有它，防止其他线程修改当前线程的APC结构。随后，检查APC对象的Inserted 域。如果是TRUE,表明这个APC对象已经存放到APC队列中了，函数立即返回FALSE.如果APC对象的Inserted 域是FALSE.函数通过ApcStateIndex域来确定目标APC环境，然后把APC对象存放到相应的APC队列中，即将APC对象中的ApcListEntry 域链入到APC环境的ApcListHead域中。链入的位置由APC的类型决定。常规的内核模式APC,用户模式APC都是存放到相应的APC队列的末端。相反的，如果队列中已经存放了一些APC对象，特殊的内核模式APC存放到队列中第一个常规内核模式APC对象的前面。如果是内核定义的一个当线程退出时使用的用户APC,它也会被放在相应的队列的前面。然后，线程的主APC环境中的UserApcPending域杯设置为TRUE。这时KiInsertQueueApc 设置APC对象的Inserted 域为TRUE，表明这个APC对象已经存放到APC队列中了。接下来，检查这个APC对象是否被排队到线程的当前进程上下文APC环境中，如果不是，函数立即返回TRUE。如果这是一个内核模式APC，线程主APC环境中的KernelApcPending域设置为TRUE。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">在WIN32 SDK文档中是这样描述APCs的： 当一个APC被成功的存放到它的队列后，发出一个软中断，APC将会在线程被调度运行的下一个时间片执行。然而这不是完全正确的。这样一个软中断，仅仅是当一个内核模式的APC（无论是常规的内核模式APC还是特殊的内核模式APC）针对于调用线程时，才会发出。随后函数返回TRUE。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">1）如果APC不是针对于调用线程，目标线程在Passive权限等级处在等待状态；<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">2）这是一个常规内核模式APC<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">3）这个线程不再临界区<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">4）没有其他的常规内核模式APC仍然在进行中<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">那么这个线程被唤醒，返回状态是STATUS_KERNEL_APC。但是等待状态没有aborted。 如果这是一个用户模式APC，KiInsertQueueApc检查判断目标线程是否是alertable等待状态，并且WaitMode域等于UserMode。如果是，主APC环境的UserApcPending 域设置为TRUE。等待状态返回STATUS_USER_APC，最后，函数释放spinlock，返回TRUE，表示APC对象已经被成功放入队列。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">早期作为APC管理函数的补充，设备驱动开发者可以使用未公开的系统服务NtQueueApcThread来直接将一个用户模式的APC投递到某个线程。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">这个函数内部实际上是调用了KeInitializeApc 和KeInsertQueueApc 来完成这个任务。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NTSYSAPI<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NTSTATUS<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NTAPI<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NtQueueApcThread (<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN HANDLE Thread,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PKNORMAL_ROUTINE NormalRoutine,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID NormalContext,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID SystemArgument1,<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">IN PVOID SystemArgument2<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">);<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NT的APC派发器<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">NT检查是否线程有未决的APCs. 然后APC派发器子程序KiDeliverApc在这个线程上下文执行来开始将未决的APC执行。注意，这个行为中断了线程的正常执行流程，首先将控制权给APC派发器，随后当KiDeliverApc完成后，继续线程的执行。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">例如：当一个线程被调度运行时，最后一步，上下文切换函数 SwapContext 用来检查是否新的线程有未决的内核APCs.如果是，SwapContext要么（1）请求一个APC级别的软中断来开始APC执行，由于新线程运行在低的IRQL（Passive级别。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">或者（2）返回TRUE，表示新的线程有未决的内核APCs。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">究竟是执行(1)还是(2)取决于新线程所处的IRQL级别. 如果它的权限级别高于Passive级,SwapContext 执行(1),如果它是在Passive级,则选择执行(2).<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">SwapContext的返回值仅仅是特定系统函数可用的,这些系统函数调用SwapContext来强制切换线程上下文到另一个线程. 然后,当这些系统函数经过一段时间再继续时,他们通常检查SwapContext 的返回值,如果是TRUE,他们就会调用APC派发器来投递内核APCs到当前的线程. 例如:<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">系统函数KiSwapThread被等待服务用来放弃处理器，直到等待结束。这个函数内部调用SwapContext。当等待结束，继续从调用SwapContext处执行时，就会检查SwapContext的返回值。如果是TRUE，KiSwapThread会降低IRQL级别到APC级，然后调用KiDeliverApc来在当前线程执行内核APCs.<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">对于用户APCs, 内核调用APC派发器仅仅是当线程回到用户模式，并且线程的主APC环境的UserApcPending域为TRUE时。例如：当系统服务派发器KiSystemService完成一个系统服务请求正打算回到用户模式时，它会检查是否有未决的用户APCs。在执行上，KiDeliverApc调用用户APC的KernelRoutine. 随后，KiInitializeUserApc函数被调用，用来设置线程的陷阱帧。所以从内核模式退出时，线程开始在用户模式下执行<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">。KiInitializeUserApc的函数的作用是拷贝当前线程先前的执行状态（当进入内核模式时，这个状态保存在线程内核栈创建的陷阱帧里），从内核栈到线程的用户模式栈，初始化用户模式APC。APC派发器子程序KiUserApcDispatcher在Ntdll.dll内。最后，加载陷阱帧的EIP寄存器和Ntdll.dll中KiUserApcDispatcher的地址。当陷阱帧最后释放时，内核将控制转交给KiUserApcDispatcher，这个函数调用APC的NormalRoutine例程，NormalRoutine函数地址以及参数都在栈中，当例程完成时，它调用NtContinue来让线程利用在栈中先前的上下文继续执行，仿佛什么事情也没有发生过。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">当内核调用KiDeliverApc来执行一个用户模式APC时，线程中的PreviousMode域被设为UserMode. TrapFrame域指向线程的陷阱帧。当内核调用KiDeliverApc来执行内核APCs时，线程中的PreviousMode域被设为KernelMode. TrapFrame域指向NULL。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">注意，无论何时只要KernelRoutine被调用，传递给它的指针是一个局部的APC属性的副本，由于APC对象已经脱离了队列，所以可以安全的在KernelRoutine中释放APC内存。此外，这个例程在它的参数被传递给其他例程之前，有一个最后的机会来修改这些参数。<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px"><br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">结论：<br style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px">APC提供了一个非常有用的机制，允许在特定的线程上下文中异步的执行代码。作为一个设备驱动开发者，你可以依赖APCs在某个特定的线程上下文中执行一个例程，而不需要线程的许可和干涉。对于用户应用程序，用户模式APCs可以用来有效地实现一些回调通知机制。</span></span><img src ="http://www.cppblog.com/shaker/aggbug/145731.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/shaker/" target="_blank">shaker(太子)</a> 2011-05-05 11:46 <a href="http://www.cppblog.com/shaker/archive/2011/05/05/145731.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>