Windows APC機制(一)

來源:互聯網
上載者:User

      非同步程序呼叫(APCs) 是NT非同步處理體繫結構中的一個基礎部分,理解了它,對於瞭解NT怎樣操作和執行幾個核心的系統操作很有協助。

1) APCs允許使用者程式和系統元件在一個進程的地址空間內某個線程的上下文中執行代碼。
2) I/O管理器使用APCs來完成一個線程發起的非同步I/O操作。例如:當一個裝置驅動調用IoCompleteRequest來通知I/O管理器,它已經結束處理一個非同步I/O請求時,I/O管理器排隊一個apc到發起請求的線程。然後線程在一個較低IRQL層級,來執行APC. APC的作用是從系統空間拷貝I/O操作結果和狀態資訊到線程虛擬記憶體空間的一個緩衝中。
3) 使用APC可以得到或者設定一個線程的上下文和掛起線程的執行。

 

上面是網上找來的,下面是MSDN上的說明:

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.

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.

 

還先看一下那些重要結構:

kd> dt KTHREAD
nt!KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 KernelStack      : Ptr32 Void
   +0x024 ThreadLock       : Uint4B
   +0x028 ApcState         : _KAPC_STATE
   +0x028 ApcStateFill     : [23] UChar
   +0x03f ApcQueueable     : UChar
   +0x040 NextProcessor    : UChar
   +0x041 DeferredProcessor : UChar
   +0x042 AdjustReason     : UChar
   +0x043 AdjustIncrement  : Char
   +0x044 ApcQueueLock     : Uint4B
   +0x048 ContextSwitches  : Uint4B
   +0x04c State            : UChar
   +0x04d NpxState         : UChar
   +0x04e WaitIrql         : UChar
   +0x04f WaitMode         : Char
   +0x050 WaitStatus       : Int4B
   +0x054 WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x054 GateObject       : Ptr32 _KGATE
   +0x058 Alertable        : UChar
   +0x059 WaitNext         : UChar
   +0x05a WaitReason       : UChar
   +0x05b Priority         : Char
   +0x05c EnableStackSwap  : UChar
   +0x05d SwapBusy         : UChar
   +0x05e Alerted          : [2] UChar
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 Queue            : Ptr32 _KQUEUE
   +0x06c WaitTime         : Uint4B
   +0x070 KernelApcDisable : Int2B
   +0x072 SpecialApcDisable : Int2B
   +0x070 CombinedApcDisable : Uint4B
   +0x074 Teb              : Ptr32 Void
   +0x078 Timer            : _KTIMER
   +0x078 TimerFill        : [40] UChar
   +0x0a0 AutoAlignment    : Pos 0, 1 Bit
   +0x0a0 DisableBoost     : Pos 1, 1 Bit
   +0x0a0 ReservedFlags    : Pos 2, 30 Bits
   +0x0a0 ThreadFlags      : Int4B
   +0x0a8 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0a8 WaitBlockFill0   : [23] UChar
   +0x0bf SystemAffinityActive : UChar
   +0x0a8 WaitBlockFill1   : [47] UChar
   +0x0d7 PreviousMode     : Char
   +0x0a8 WaitBlockFill2   : [71] UChar
   +0x0ef ResourceIndex    : UChar
   +0x0a8 WaitBlockFill3   : [95] UChar
   +0x107 LargeStack       : UChar
   +0x108 QueueListEntry   : _LIST_ENTRY
   +0x110 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x114 CallbackStack    : Ptr32 Void
   +0x118 ServiceTable     : Ptr32 Void
   +0x11c ApcStateIndex    : UChar
   +0x11d IdealProcessor   : UChar
   +0x11e Preempted        : UChar
   +0x11f ProcessReadyQueue : UChar
   +0x120 KernelStackResident : UChar
   +0x121 BasePriority     : Char
   +0x122 PriorityDecrement : Char
   +0x123 Saturation       : Char
   +0x124 UserAffinity     : Uint4B
   +0x128 Process          : Ptr32 _KPROCESS
   +0x12c Affinity         : Uint4B
   +0x130 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x138 SavedApcState    : _KAPC_STATE
   +0x138 SavedApcStateFill : [23] UChar
   +0x14f FreezeCount      : Char
   +0x150 SuspendCount     : Char
   +0x151 UserIdealProcessor : UChar
   +0x152 CalloutActive    : UChar
   +0x153 Iopl             : UChar
   +0x154 Win32Thread      : Ptr32 Void
   +0x158 StackBase        : Ptr32 Void
   +0x15c SuspendApc       : _KAPC
   +0x15c SuspendApcFill0  : [1] UChar
   …………

   …………

上面紅色部分是APC機制用到的幾個欄位!!

 

kd> dt _KAPC_STATE
nt!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY,每個指標指向_KAPC結構
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar

顯然,這裡的 ApcListHead 就是 APC 隊列頭。不過這是個大小為 2 的數組,說明實際
上(每個線程)有兩個 APC 隊列。這是因為 APC 函數分為使用者 APC 和核心 APC 兩種,各有
各的隊列。所謂使用者 APC,是指相應的 APC 函數位於使用者空間、在使用者空間執行;而核心
APC,則相應的 APC 函數為核心功能。

SavedApcState也是個_KAPC_STATE結構,噹噹前程暫時“掛靠(Attach)”到另一個進程的地址空間的時侯,ApcState就拷貝到SavedApcState暫時存放!

當然,還要有狀態資訊說明本線程當前是處於“原始環境”還是“掛靠環境”,這就是 ApcStateIndex 的作用,代碼中為 ApcStateIndex的值定義了一種枚舉類型:

typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
    AttachedApcEnvironment,
    CurrentApcEnvironment,
    InsertApcEnvironment
} KAPC_ENVIRONMENT;

實際可用於 ApcStateIndex 的只是 OriginalApcEnvironment和 AttachedApcEnvironment。

KAPC_STATE 指標數組 ApcStatePointer[2],就用ApcStateIndex 的當前值作為下標,而數組中的指標則根據情況可以分別指向兩個APC_STATE 資料結構中的一個。

kd> dt _KAPC  ;APC對象
nt!_KAPC
   +0x000 Type             : UChar
   +0x001 SpareByte0       : UChar
   +0x002 Size             : UChar
   +0x003 SpareByte1       : UChar
   +0x004 SpareLong0       : Uint4B
   +0x008 Thread           : Ptr32 _KTHREAD
   +0x00c ApcListEntry     : _LIST_ENTRY
   +0x014 KernelRoutine    : Ptr32     void
   +0x018 RundownRoutine   : Ptr32     void
   +0x01c NormalRoutine    : Ptr32     void
   +0x020 NormalContext    : Ptr32 Void
   +0x024 SystemArgument1  : Ptr32 Void
   +0x028 SystemArgument2  : Ptr32 Void
   +0x02c ApcStateIndex    : Char
   +0x02d ApcMode          : Char
   +0x02e Inserted         : UChar

 KernelRoutine、RundownRoutine、NormalRoutine。其中只有 NormalRoutine才指向(執行)APC 函數的要求者所提供的函數,其餘兩個都是輔助性的!

 

NTKERNELAPI
VOID
KeInitializeApc (
    __out PRKAPC Apc,
    __in PRKTHREAD Thread,
    __in KAPC_ENVIRONMENT Environment,
    __in PKKERNEL_ROUTINE KernelRoutine,
    __in_opt PKRUNDOWN_ROUTINE RundownRoutine,
    __in_opt PKNORMAL_ROUTINE NormalRoutine,
    __in_opt KPROCESSOR_MODE ProcessorMode,
    __in_opt PVOID NormalContext
    );

這個函數主要用來初始化Apc(_KAPC)這個結構的,如果Environment==CurrentApcEnvironment,Apc->ApcStateIndex就由KTHREAD中的ApcStateIndex決定,但Environment不能大於KTHREAD中的ApcStateIndex!

最後,APC 請求的模式ProcessorMode,但是有個例外,那就是:如果指標NormalRoutine 為 0,那麼實際的模式變成了 KernelMode。這是因為在這種情況下沒有使用者空間APC函數可以執行, 唯一將得到執行的是KernelRoutine!

最後,KeInitializeApc 設定Inserted域為FALSE。然而初始化APC對象,並沒有把它存放到相應的APC隊列中。

 

NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
    __inout PRKAPC Apc,
    __in_opt PVOID SystemArgument1,
    __in_opt PVOID SystemArgument2,
    __in KPRIORITY Increment
    );

據APC請求的具體情況,有時候要插在隊列的前頭,一般則掛在隊列的尾部。

 

_KiServiceExit:

        cli                                         ; disable interrupts
        DISPATCH_USER_APC   ebp, ReturnCurrentEax

;
; Exit from SystemService
;

        EXIT_ALL    NoRestoreSegs, NoRestoreVolatile   ;這個宏以後再講

 

DISPATCH_USER_APC   macro   TFrame, ReturnCurrentEax
local   a, b, c
c:
.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)

        test    byte ptr [TFrame]+TsEflags+2, EFLAGS_V86_MASK/010000h ; is previous mode v86?
        jnz     short b                             ; if nz, yes, go check for APC
        test    byte ptr [TFrame]+TsSegCs,MODE_MASK ; is previous mode user mode?
        jz      a                                   ; No, previousmode=Kernel, jump out
b:      mov     ebx, PCR[PcPrcbData+PbCurrentThread]; get addr of current thread
        mov     byte ptr [ebx]+ThAlerted, 0         ; clear kernel mode alerted
        cmp     byte ptr [ebx]+ThApcState.AsUserApcPending, 0
        je      a                                   ; if eq, no user APC pending

        mov     ebx, TFrame
ifnb <ReturnCurrentEax>;條件宏彙編,如果ReturnCurrentEax參數不為空白,則編譯!

                                       ;DISPATCH_USER_APC   ebp, ReturnCurrentEax,顯然這裡是編譯的!
        mov     [ebx].TsEax, eax        ; Store return code in trap frame
        mov     dword ptr [ebx]+TsSegFs, KGDT_R3_TEB OR RPL_MASK
        mov     dword ptr [ebx]+TsSegDs, KGDT_R3_DATA OR RPL_MASK
        mov     dword ptr [ebx]+TsSegEs, KGDT_R3_DATA OR RPL_MASK
        mov     dword ptr [ebx]+TsSegGs, 0
endif

;
; Save previous IRQL and set new priority level
;
        RaiseIrql APC_LEVEL
        push    eax                     ; Save OldIrql

        sti                             ; Allow higher priority ints

;
; call the APC delivery routine.
;
; ebx - Trap frame
; 0 - Null exception frame
; 1 - Previous mode
;
; call APC deliver routine
;

        stdCall _KiDeliverApc, <1, 0, ebx> ;1就是UserMode

        pop     ecx                     ; (ecx) = OldIrql
        LowerIrql ecx

ifnb <ReturnCurrentEax>            ;同上分析
        mov     eax, [ebx].TsEax        ; Restore eax, just in case
endif

        cli
        jmp     b                         ; 注意這個迴圈!!

    ALIGN 4
a:
endm

 

這段代碼主要檢查:

  • 即將返回的是否使用者空間。
  • 是否有使用者APC請求正在等待執行

條件符合才用KiDeliverApc真正投遞APC。注意代碼jmp b,好像在返回使用者空間前KiDeliverApc會被迴圈調用直到沒有user APC,其實不是的,KiDeliverApc每處理完一個User APC就把UserApcPending清零,所以User APCs在返回使用者空間時還是只能投遞一次!KiDeliverApc中用while處理完所有Kernel Mode APCs,但User Mode APC卻只處理一個!事實上User APC的投遞是很特殊的,以後會講到,而且每次只能投遞一次!

前面講過,KTHREAD 中有兩個 KAPC_STATE 資料結構,一個是 ApcState,另一個是SavedApcState,二者都有APC 隊列,但是要投遞的只是ApcState 中的隊列。

KiDeliverApc (
    IN KPROCESSOR_MODE PreviousMode,//寫成DeliverMode不是更好
    IN PKEXCEPTION_FRAME ExceptionFrame,//這個參數幾乎就是0
    IN PKTRAP_FRAME TrapFrame
    )

這個函數裡面還比較複雜,代碼不帖了。

參數PreviousMode表示需要“投遞”哪一種 APC,可以是UserMode,也可以是KernelMode。不過,KernelMode 確實表示只要求執行核心 APC,而UserMode 卻表示在執行核心 APC 之外再執行使用者APC。

 

 

The Windows operating system uses three kinds of APCs:

  • 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)
  • 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.
  • 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.

從代碼的角度看是這樣的:

User APCs
_KAPC.ApcMode==UserMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine!=NULL

 

Normal kernel APCs

_KAPC.ApcMode==KernelMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine!=NULL

 

Special kernel APCs

_KAPC.ApcMode==KernelMode,_KAPC.KernelRoutine!=NULL,_KAPC.NormolRoutine==NULL

 

有一點它們的_KAPC.KernelRoutine肯定不為空白。並且,如果NormolRoutine也不為空白,那麼KernelRoutine都在NormolRoutine被調用前被調用!!

 

未完待續……

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.