• 异步机制


    1 KQUEUE KeInitializeQueue

    VOID

      KeInitializeQueue(

        IN PKQUEUE  Queue,

        IN ULONG  Count  OPTIONAL

        );

    lkd> dt _KQUEUE

    nt!_KQUEUE

       +0x000 Header           : _DISPATCHER_HEADER

       +0x010 EntryListHead    : _LIST_ENTRY

       +0x018 CurrentCount     : Uint4B

       +0x01c MaximumCount     : Uint4B

       +0x020 ThreadListHead   : _LIST_ENTRY

    KeInitializeQueue初始化Queue对象,线程可以等待访问队列中的元素,Count 表示同时可以访问队列的线程数(不需要等待),一般使用KQUEUE的时候也会创建对应数量的线程来queue and dequeue its entries

    2 KQUEUE KeInsertQueue

    LONG

      KeInsertQueue(

        IN PKQUEUE  Queue,

        IN PLIST_ENTRY  Entry

        );

    如果当前线程的不是处于queue wait状态,或者等待的QUEUE不是要插入的这个QUEUE,并且有其它线程在等待KQUEUE(调用KeRemoveQueue),活跃的线程小于最大的活跃线程,则会唤醒等待KQUEUE的线程来取本次要插入的Entry,否则把Entry插入到Queue的尾部.

    3 CreateIoCompletionPort - ExistingCompletionPort为NULL的情况

    HANDLE WINAPI CreateIoCompletionPort(

      __in          HANDLE FileHandle,

      __in          HANDLE ExistingCompletionPort,

      __in          ULONG_PTR CompletionKey,

      __in          DWORD NumberOfConcurrentThreads

    );

    内部创建一个完成端口对象,对象开始是一个_KQUEUE对象,然后调用KeInitializeQueue初始化这个KQUEUE,然后返回这个对象的句柄。如果FileHandle不为NULL,则调用NtSetInformationFile把FileHandle和完成端口进行绑定关联

          CompletionInfo.Port = ExistingCompletionPort;

          CompletionInfo.Key = (void *)CompletionKey;

          NtSetInformationFile(FileHandle, &IoSb, &CompletionInfo, 8, FileCompletionInformation);

    关于NtSetInformationFile内部的实现,先看FILE_OBJECT对象

    lkd> dt _FILE_OBJECT

    nt!_FILE_OBJECT

    ...

       +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT

    lkd> dt  _IO_COMPLETION_CONTEXT

    nt!_IO_COMPLETION_CONTEXT

       +0x000 Port             : Ptr32 Void

       +0x004 Key              : Ptr32 Void

    也就是说内部先分配一个IO_COMPLETION_CONTEXT结构体,然后根据CompletionInfo里面的Port句柄获得Port对象,然后填充这个结构体,最后把这个结构体的指针放到FileHandle对应的文件对象的CompletionContext里面。一个CompletionPort可以关联多个FileHandle,可以通过CompletionKey来标识具体的FileHandle

    4 PostQueuedCompletionStatus

    BOOL WINAPI PostQueuedCompletionStatus(

      __in          HANDLE CompletionPort,

      __in          DWORD dwNumberOfBytesTransferred,

      __in          ULONG_PTR dwCompletionKey,

      __in          LPOVERLAPPED lpOverlapped //这里只是个指针,不一定非要指向OVERLAPPED结构体

    );

    往CompletionPort投递一个I/O completion packet,包含dwNumberOfBytesTransferred, dwCompletionKey和lpOverlapped.

    PostQueuedCompletionStatus内部调用NtSetIoCompletion来实现具体的功能

    NTSTATUS

    NtSetIoCompletion (

        __in HANDLE IoCompletionHandle, //CompletionPort

        __in PVOID KeyContext,     //dwCompletionKey

        __in_opt PVOID ApcContext,      //为0

        __in NTSTATUS IoStatus,     //lpOverlapped

        __in ULONG_PTR IoStatusInformation //dwNumberOfBytesTransferred

        )

    NtSetIoCompletion内部大概实现如下

    IoCompletionHandle获得完成端口对象IoCompletion,分配一个结构体_IOP_MINI_COMPLETION_PACKET MiniPacket,根据NtSetIoCompletion传进的参数初始化MiniPacket,调用KeInsertQueue((PKQUEUE)IoCompletion, &MiniPacket->ListEntry);插入到完成端口对象的KQUEUE里面_IOP_MINI_COMPLETION_PACKETL里面的PacketType表示类型,比如IopCompletionPacketIrp表示是IRP IO完成时投递的,在NtRemoveIoCompletion里面会跟进这个字段来区分数据

    5 GetQueuedCompletionStatus

    BOOL WINAPI GetQueuedCompletionStatus(

      __in          HANDLE CompletionPort,

      __out         LPDWORD lpNumberOfBytes,

      __out         PULONG_PTR lpCompletionKey,

      __out         LPOVERLAPPED* lpOverlapped,

      __in          DWORD dwMilliseconds

    );

    内部只是简单的调用NtRemoveIoCompletion

    NTSTATUS

    NtRemoveIoCompletion (

        __in HANDLE IoCompletionHandle,

        __out PVOID *KeyContext,

        __out PVOID *ApcContext,

        __out PIO_STATUS_BLOCK IoStatusBlock,

        __in_opt PLARGE_INTEGER Timeout

        )

    如果是IO

       ApcContext = Irp->Overlay.AsynchronousParameters.UserApcContext;

       KeyContext = (PVOID)Irp->Tail.CompletionKey;

       IoStatusBlock = Irp->IoStatus;

    6 完成端口跟FileHandle的内部逻辑

    当一次IO完成时,会调用IopCompleteRequest,IopCompleteRequest内部有这一处逻辑

            if (fileObject && fileObject->CompletionContext) {

                port = fileObject->CompletionContext->Port;

                key = fileObject->CompletionContext->Key;

            }

                irp->Tail.CompletionKey = key;

                irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;

                KeInsertQueue( (PKQUEUE) port,

                               &irp->Tail.Overlay.ListEntry );

    也就是说,每一次IO完成时,都会向完成端口投递相关信息

    7  BindIoCompletionCallback

    BOOL WINAPI BindIoCompletionCallback(

      __in          HANDLE FileHandle,

      __in          LPOVERLAPPED_COMPLETION_ROUTINE Function,

      ULONG Flags //没用

    );

    微软内部帮你封装好了IO 完成端口的调用,线程池,在文件操作完成时会调用Function

    BindIoCompletionCallback内部实现

        Status = RtlSetIoCompletionCallback(FileHandle,

                                            (PIO_APC_ROUTINE)Function,

                                            Flags);

    RtlSetIoCompletionCallback内部只是把Function当做一个CompletionKey,调用 NtSetInformationFile(FileHandle, &IoSb, &CompletionInfo, 8, FileCompletionInformation); 把Function和文件句柄进行绑定,下次有IO完成时,线程池里面的线程调用ZwRemoveIoCompletion就可以获得CompletionKey,然后进行调用

    VOID CALLBACK FileIOCompletionRoutine(

      [in]                 DWORD dwErrorCode,  //

      [in]                 DWORD dwNumberOfBytesTransfered,

      [in]                 LPOVERLAPPED lpOverlapped //ApcContext = Irp->Overlay.AsynchronousParameters.UserApcContext;

    );

    那Irp->Overlay.AsynchronousParameters.UserApcContext从哪来的,在NtReadFile, NtWriteFile里面会设置

    NTSTATUS

    NtReadFile (

        __in HANDLE FileHandle,

        __in_opt HANDLE Event,

        __in_opt PIO_APC_ROUTINE ApcRoutine,

        __in_opt PVOID ApcContext,

        __out PIO_STATUS_BLOCK IoStatusBlock,

        __out_bcount(Length) PVOID Buffer,

        __in ULONG Length,

        __in_opt PLARGE_INTEGER ByteOffset,

        __in_opt PULONG Key

        )

    irp->Overlay.AsynchronousParameters.UserApcContext = ApcContext;

    对应的就是ReadFile里面的lpOverlapped

    BOOL WINAPI ReadFile(

      __in          HANDLE hFile,

      __out         LPVOID lpBuffer,

      __in          DWORD nNumberOfBytesToRead,

      __out         LPDWORD lpNumberOfBytesRead,

      __in          LPOVERLAPPED lpOverlapped

    );

    8 APC和完成端口

    如果绑定了完成端口,那APC是不能干活的,在NtReadFile, NtWriteFile里面会这么判断

                if (fileObject->CompletionContext && IopApcRoutinePresent( ApcRoutine )) {

                    ObDereferenceObject( fileObject );

                    return STATUS_INVALID_PARAMETER;

                }

    也就是意味着绑定了完成端口以后,你不能使用ReadFileEx传入回调

  • 相关阅读:
    Using AlloyTouch to control three.js 3D model
    AlloyTouch与three.js 3D模型交互
    移动Web触摸与运动解决方案AlloyTouch开源啦
    transformjs玩转星球
    swing with transformjs
    和transformjs一起摇摆
    getting started with transformjs
    移动Web利器transformjs入门
    腾讯AlloyTeam移动Web裁剪组件AlloyCrop正式开源
    Why AlloyFinger is so much smaller than hammerjs?
  • 原文地址:https://www.cnblogs.com/sysnap/p/4415300.html
Copyright © 2020-2023  润新知