• 重叠I/O之使用完成例程的扩展I/O【系列二】


    一 废话

      在上一篇文章中,我们介绍了通过等待内核对象来接受I/O完成通知的重叠I/O。除了使用同步对象外,我们还可以使用其它方法,这便是这篇文章要介绍的使用完成例程的扩展I/O。完成例程其实就是回调函数,当I/O完成的时候系统调用一个用户指定的回调函数来通知用户I/O完成, 调用完回调函数之后,可以继续启动下一个I/O操作。为了实现回调,线程需要处于可通知的状态。为什么称之为“扩展I/O”呢?因为它是等待内核对象的异步I/O的扩展,而且它需要调用扩展函数。

    二 相关数据结构和函数

      1 ReadFileEx 和 WriteFileEx

      为什么是ReadFileEx和WriteFileEx,而不是使用函数ReadFile和WriteFile?额。。。一是因为当I/O完成的时候需要调用用户设置的回调函数,而回调函数的地址怎么和相关I/O异步过程调用队列相关联;二是ReadFile和WriteFile发送I/O操作请求时,异步情况下会立刻返回,所以函数参数中的已传输字节数这个参数是没有用的。所以,我们需要新的函数ReadFileEx和WriteFileEx,下面是两个函数的原型:

    BOOL
    WINAPI
    ReadFileEx(
                HANDLE hFile,
                (FILE) LPVOID lpBuffer,
                DWORD nNumberOfBytesToRead,
                LPOVERLAPPED lpOverlapped,
                LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
               );
    
    BOOL
    WINAPI
    WriteFileEx(
                 HANDLE hFile,
                 (nNumberOfBytesToWrite) LPCVOID lpBuffer,
                 DWORD nNumberOfBytesToWrite,
                 LPOVERLAPPED lpOverlapped,
                 LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
                );

       ReadFileEx和WriteFileEx的前三个参数和ReadFile和WriteFile中的一样。lpOverlapped必须提供提供OVERLAPPED结构,但是不用设置hEvent成员,系统将忽略它。但是,可以将其设置为表示I/O操作的信息,比如顺序号。lpCompletionRoutine是所要设置的I/O回调函数的地址,回调函数的原型为:

    VOID
    (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(
    	__in    DWORD dwErrorCode,
    	__in    DWORD dwNumberOfBytesTransfered,
    	__inout LPOVERLAPPED lpOverlapped
    	);
    

      

      2 可提醒的等待函数

      当I/O请求完成时候,系统会将它们添加到线程的APC队列中——回调函数并不会立即被调用,这是因为线程可能还在忙于其它的事情。为了对线程APC队列中的项进行处理,线程必须将自己设置为可提醒状态。这样当线程执行到可提醒状态点时,而APC队列中刚好有已经完成的I/O操作,则会调用回到函数。Windws共提供了6个函数可将线程置为可提醒状态:

    WINBASEAPI
    DWORD
    WINAPI
    SleepEx(
            __in DWORD dwMilliseconds,
            __in BOOL bAlertable
            );
    
    
    WINBASEAPI
    DWORD
    WINAPI
    WaitForSingleObjectEx(
                          __in HANDLE hHandle,
                          __in DWORD dwMilliseconds,
                          __in BOOL bAlertable
                          );
    
    
    WINBASEAPI
    DWORD
    WINAPI
    WaitForMultipleObjectsEx(
                             __in DWORD nCount,
                             __in_ecount(nCount) CONST HANDLE *lpHandles,
                             __in BOOL bWaitAll,
                             __in DWORD dwMilliseconds,
                             __in BOOL bAlertable
                             );
    
    
    WINBASEAPI
    DWORD
    WINAPI
    SignalObjectAndWait(
                        __in HANDLE hObjectToSignal,
                        __in HANDLE hObjectToWaitOn,
                        __in DWORD dwMilliseconds,
                        __in BOOL bAlertable
                        );
    
    BOOL
    WINAPI
    GetQueuedCompletionStatusEx(
                                __in  HANDLE CompletionPort,
                                __out_ecount_part(ulCount, *ulNumEntriesRemoved) LPOVERLAPPED_ENTRY lpCompletionPortEntries,
                                __in  ULONG ulCount,
                                __out PULONG ulNumEntriesRemoved,
                                __in  DWORD dwMilliseconds,
                                __in  BOOL fAlertable
                                );
    
    DWORD
    WINAPI
    MsgWaitForMultipleObjectsEx(
                                __in DWORD nCount,
                                __in_ecount_opt(nCount) CONST HANDLE *pHandles,
                                __in DWORD dwMilliseconds,
                                __in DWORD dwWakeMask,
                                __in DWORD dwFlags);  

      前五个函数的最后一个参数是一个布尔值,表示调用线程是否应该将自己置为可提醒状态。最后一个函数MsgWaitForMutipleObjectsEx需要使用MWMO_ALTERABLE来让线程进入可提醒状态。返回值表示它们返回的原因,如果返回的或者通过GetLastError为WAIT_IO_COMPLETION,表示线程至少处理了APC队列中的一项。

      注意:

    • 对任何可提醒的等待函数使用INFINITE超时值。
    • 使用重叠结构中的hEvent数据成员来将信息传递给回调函数。

      3  异步过程调用队列(asynchronous procedure call, APC)

      到底完成例程的I/O操作是怎么运转的呢?我们从头开始梳理。这就需要了解异步过程调用队列(asynchronous procedure call, APC),APC队列是由系统在内部维护的。当系统创建一个线程的时候,会同是创建一个与之相关联的队列,称之为异步过程调用。当我们调用ReadFileEx或WriteFileEx向设备驱动程序发出一个I/O请求后立刻返回,但是会将回调函数的地址传给设备驱动程序。当设备驱动程序完成I/O请求的时候,便会在发出I/O请求的线程的APC队列中添加一项。该项包含了完成函数的地址,以及发出此I/O请求所使用的OVERLAPPED结构的地址。

      当我们调用可提醒函数将线程设置为可提醒状态时,系统会首先检查线程的APC队列。如果队列中至少有一项,系统便会将APC队列中的那一项取出,让线程调用回调函数,并在OVERLAPPED结构中传入已完成I/O请求的错误码,已传输的字节数,以及OVERLAPPED结构的地址。当回调函数返回的时候,系统会检查APC队列是否还有其它的项,如果还有则继续处理下一项。即当一个线程进入可提醒状态时,该线程的APC队列中的所有完成例程都会得到执行。注意,系统会以任意的顺序执行我们添加到队列中的I/O请求。

    三 示例

  • 相关阅读:
    面试
    二叉树- 二叉树直径
    排序算法
    JAVA编程
    JAVA编程
    JAVA中break和continue的区别
    HTTP的序列化和反序列化
    PL/SQL基础
    G. Game Design
    hdu 6703 array
  • 原文地址:https://www.cnblogs.com/1314NNNKKK/p/3710230.html
Copyright © 2020-2023  润新知