Overlapped I/O模型深入分析

Overlapped I/O模型深入分析
简述:
     Overlapped I/O也称 Asynchronous  I/O,异步I/O模型。异步I/O和同步I/O不同,同步I/O时,程序被挂起,一直到I/O处理完,程序才能获得控制。异步I/O,调用一个函数告诉OS,进行I/O操作,不等I/O结束就立即返回,继续程序执行,操作系统完成I/O之后,通知消息给你。Overlapped I/O只是一种模型,它可以由内核对象(hand),事件内核对象(hEvent), 异步过程调用(apcs) 和完成端口(I/O completion)实现。
 
Overlapped I/O的设计的目的:
     取代多线程功能,(多线程存在同步机制,错误处理,在成千上万个线程 I/O中,线程上下文切换是十分消耗CPU资源的)。
     Overlapped I/O模型是 OS为你传递数据,完成上下文切换,在处理完之后通知你。由程序中的处理,变为OS的处理。内部也是用线程处理的。
 
Overlapped数据结构:
 
typedef struct _OVERLAPPED {
         DWORD   Internal;      通常被保留,当 GetOverlappedResult()传回False并且GatLastError()并非传回ERROR_IO_PENDINO时,该状态置为系统定的状态。
DWORD    InternalHigh;  通常被保留,当 GetOverlappedResult()传回False时,为
                        被传输数据的长度。
DWORD    Offset;        指定文件的位置,从该位置传送数据,文件位置是相对文件开始
处的字节偏移量。调用 ReadFile或WriteFile函数之前调用进
程设置这个成员,读写命名管道及通信设备时调用进程忽略这
个成员;
DWORD    OffsetHigh;    指定开始传送数据的字节偏移量的高位字,读写命名管道及通
信设备时调用进程忽略这个成员;
HANDLE hEvent;          标识事件,数据传送完成时把它设为信号状态,调用ReadFile
                          WriteFile   ConnectNamedPipe   TransactNamedPipe 函数
前,调用进程设置这个成员. 相关函数 
CreateEvent  ResetEvent   GetOverlappedResult  
WaitForSingleObject   CWinThread   GetLastError   
} OVERLAPPED, *LPOVERLAPPED;
二个重要功能:
1. 标识每个正在 overlapped 的操作。
2. 程序和系统之间提供了共享区域。参数可以在区域内双向传递。
 
OVERLAPPED和数据缓冲区释放问题 :
在请求时,不能释放,只有在 I/O请求完成之后,才可以释放。如果发出多个overlapped请求,每个overlapped读写操作,都必须包含文件位置(socket),另外,如果有多个磁盘,I/O执行次序无法保证。(每个overlapped都是独立的请求操作)。
 
 
内核对象(hand)实现:
例子:用 overlapped模型读一个磁盘文件内容。
   1.把设备句柄看作同步对象, ReadFile将设备句柄设为无信号。ReadFile 异步I/O字节位置必须在OVERLAPPED结构中指定。
   2.完成 I/O,设置信息状态。为有信号。
   3. WaitForSingleObject或WaitForMultipleObject判断
或者异步设备调用 GetOverLappedResult函数。
 
int main()
{
    BOOL rc;
    HANDLE hFile;
    DWORD numread;
    OVERLAPPED overlap;
    char buf[READ_SIZE];
    char szPath[MAX_PATH];
    CheckOsVersion();
 
    GetWindowsDirectory(szPath, sizeof(szPath));
    strcat(szPath, "//WINHLP32.EXE");
    hFile = CreateFile( szPath,
                    GENERIC_READ,
                    FILE_SHARE_READ|FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    FILE_FLAG_OVERLAPPED,
                    NULL
                );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Could not open %s/n", szPath);
        return -1;
    }
 
    memset(&overlap, 0, sizeof(overlap));
    overlap.Offset = 1500;
 
    rc = ReadFile(
                hFile,
                buf,
                READ_SIZE,
                &numread,
                &overlap
            );
    printf("Issued read request/n");
    if (rc)
    {
        printf("Request was returned immediately/n");
    }
    else
    {
        if (GetLastError() == ERROR_IO_PENDING)
        {
            printf("Request queued, waiting.../n");
            WaitForSingleObject(hFile, INFINITE);
            printf("Request completed./n");
            rc = GetOverlappedResult(
                                    hFile,
                                    &overlap,
                                    &numread,
                                    FALSE
                                );
            printf("Result was %d/n", rc);
        }
        else
        {
            printf("Error reading file/n");
        }
    }
    CloseHandle(hFile);
    return EXIT_SUCCESS;
}
 
事件内核对象(hEvent):
内核对象 (hand)实现的问题:
    不能区分那一个 overlapped操作,对同一个文件handle,系统有多个异步操作时(一边读文件头,一边写文件尾, 有一个完成,就会有信号,不能区分是那种操作。),为每个进行中的overlapped调用GetOverlappedResult是不好的作法。
 
事件内核对象 (hEvent)实现方案:
Overlapped成员 hEven标识事件内核对象。CreateEvent,为每个请求创建一个事件,初始化每个请求的hEvent成员(对同一文件多个读写请求,每个操作绑定一个event对象)。调用WaitForMultipleObject来等等其中一个(或全部)完成。
    另外 Event对象必须是手动重置。使用自动重置(在等待event之前设置, WaitForSingleObject() 和 WaitForMultipleObjects()函数永不返回)。
自动重置事件
WaitForSingleObject() 和 WaitForMultipleObjects()会等待事件到信号状态,随后又自动将其重置为非信号状态,这样保证了等待此事件的线程中只有一个会被唤醒。
手动重置事件
需要用户调用ResetEvent()才会重置事件。可能有若干个线程在等待同一事件,这样当事件变为信号状态时,所有等待线程都可以运行了。 SetEvent()函数用来把事件对象设置成信号状态,ResetEvent()把事件对象重置成非信号状态,两者均需事件对象句柄作参数。
 
相关例子如下:
int main()
{
    int i;
    BOOL rc;
    char szPath[MAX_PATH];
 
    CheckOsVersion();
    GetWindowsDirectory(szPath, sizeof(szPath));
    strcat(szPath, "//WINHLP32.EXE");
    ghFile = CreateFile( szPath,
                    GENERIC_READ,
                    FILE_SHARE_READ|FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    FILE_FLAG_OVERLAPPED,
                    NULL
                );
    if (ghFile == INVALID_HANDLE_VALUE)
    {
        printf("Could not open %s/n", szPath);
        return -1;
    }
 
    for (i=0; i<MAX_REQUESTS; i++)
    {
         QueueRequest(i, i*16384, READ_SIZE);
    }
 
    printf("QUEUED!!/n");
 
    MTVERIFY( WaitForMultipleObjects(
               MAX_REQUESTS, ghEvents, TRUE, INFINITE
        ) != WAIT_FAILED );
 
    for (i=0; i<MAX_REQUESTS; i++)
    {
        DWORD dwNumread;
 
        rc = GetOverlappedResult(
                                ghFile,
                                &gOverlapped[i],
                                &dwNumread,
                                FALSE
                            );
        printf("Read #%d returned %d. %d bytes were read./n",
                    i, rc, dwNumread);
        CloseHandle(gOverlapped[i].hEvent);
    }
 
    CloseHandle(ghFile);
    return EXIT_SUCCESS;
}
 
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
    int i;
    BOOL rc;
    DWORD dwNumread;
    DWORD err;
 
    MTVERIFY(
        ghEvents[nIndex] = CreateEvent(
                     NULL,    // No security
                     TRUE,    // Manual reset - extremely important!
                     FALSE,   // Initially set Event to non-signaled state
                     NULL     // No name
                    )
    );
    gOverlapped[nIndex].hEvent = ghEvents[nIndex];
    gOverlapped[nIndex].Offset = dwLocation;
 
    for (i=0; i<MAX_TRY_COUNT; i++)
     {
        rc = ReadFile(
            ghFile,
            gBuffers[nIndex],
            dwAmount,
            &dwNumread,
            &gOverlapped[nIndex]
        );
 
        if (rc)
        {
            printf("Read #%d completed immediately./n", nIndex);
            return TRUE;
        }
        err = GetLastError();
 
        if (err == ERROR_IO_PENDING)
        {
            // asynchronous i/o is still in progress
            printf("Read #%d queued for overlapped I/O./n", nIndex);
            return TRUE;
        }
 
        if ( err == ERROR_INVALID_USER_BUFFER ||
             err == ERROR_NOT_ENOUGH_QUOTA ||
             err == ERROR_NOT_ENOUGH_MEMORY )
        {
            Sleep(50); // Wait around and try later
            continue;
        }
        break;
    }
    printf("ReadFile failed./n");
    return -1;
}
 
异步过程调用(apcs):
事件内核对象 (hEvent)的问题:
    事件内核对象在使用 WaitForMultipleObjects时,只能等待64个对象。需要另建两个数据组,并gOverlapped[nIndex].hEvent = ghEvents[nIndex]绑定起来。
 
异步过程调用 (apcs)实现方案:
    异步过程调用, callback回调函数,在一个Overlapped I/O完成之后,系统调用该回调函数。OS在有信号状态下(设备句柄),才会调用回调函数(可能有很多APCS等待处理了),传给它完成I/O请求的错误码,传输字节数和Overlapped结构的地址。
    五个函数可以设置信号状态:
1. SleepEx
2. WaitForSingleObjectEx
3. WaitForMultipleObjectEx
4. SingalObjectAndWait
5. MsgWaitForMultipleObjectsEx
 
Main函数调用 WaitForSingleObjectEx, APCS被处理,调用回调函数
FileIOCompletionRoutine
 
 
VOID WINAPI FileIOCompletionRoutine(
    DWORD dwErrorCode, // completion code
    DWORD dwNumberOfBytesTransfered,    // number of bytes transferred
    LPOVERLAPPED lpOverlapped   // pointer to structure with I/O information 
   )
{
    int nIndex = (int)(lpOverlapped->hEvent);
    printf("Read #%d returned %d. %d bytes were read./n",
        nIndex,
        dwErrorCode,
        dwNumberOfBytesTransfered);
 
    if (++nCompletionCount == MAX_REQUESTS)
         SetEvent(ghEvent); // Cause the wait to terminate
}
 
int main()
{
    int i;
    char szPath[MAX_PATH];
    CheckOsVersion();
 
    MTVERIFY(
        ghEvent = CreateEvent(
                     NULL,    // No security
                     TRUE,    // Manual reset - extremely important!
                     FALSE,   // Initially set Event to non-signaled state
                     NULL     // No name
                    )
    );
 
    GetWindowsDirectory(szPath, sizeof(szPath));
    strcat(szPath, "//WINHLP32.EXE");
  
    ghFile = CreateFile( szPath,
                    GENERIC_READ,
                    FILE_SHARE_READ|FILE_SHARE_WRITE,
                    NULL,
                    OPEN_EXISTING,
                    FILE_FLAG_OVERLAPPED,
                    NULL
                );
    if (ghFile == INVALID_HANDLE_VALUE)
    {
        printf("Could not open %s/n", szPath);
        return -1;
    }
 
    for (i=0; i<MAX_REQUESTS; i++)
{
        QueueRequest(i, i*16384, READ_SIZE);
    }
    printf("QUEUED!!/n");
 
    for (;;)
    {
        DWORD rc;
        rc = WaitForSingleObjectEx(ghEvent, INFINITE, TRUE );
        if (rc == WAIT_OBJECT_0)
            break;
        MTVERIFY(rc == WAIT_IO_COMPLETION);
    }
 
    CloseHandle(ghFile);
    return EXIT_SUCCESS;
}
 
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
    int i;
    BOOL rc;
    DWORD err;
 
    gOverlapped[nIndex].hEvent = (HANDLE)nIndex;
    gOverlapped[nIndex].Offset = dwLocation;
 
    for (i=0; i<MAX_TRY_COUNT; i++)
    {
        rc = ReadFileEx(
            ghFile,
            gBuffers[nIndex],
            dwAmount,
            &gOverlapped[nIndex],
            FileIOCompletionRoutine
        );
 
        if (rc)
        {
            printf("Read #%d queued for overlapped I/O./n", nIndex);
            return TRUE;
        }
        err = GetLastError();
 
        if ( err == ERROR_INVALID_USER_BUFFER ||
             err == ERROR_NOT_ENOUGH_QUOTA ||
             err == ERROR_NOT_ENOUGH_MEMORY )
        {
            Sleep(50); // Wait around and try later
            continue;
        }
        break;
    }
 
    printf("ReadFileEx failed./n");
    return -1;
}
 
完成端口(I/O completion):
异步过程调用 (apcs)问题:
    只有发 overlapped请求的线程才可以提供callback函数(需要一个特定的线程为一个特定的I/O请求服务)。
 
完成端口 (I/O completion)的优点:
    不会限制 handle个数,可处理成千上万个连接。I/O completion port允许一个线程将一个请求暂时保存下来,由另一个线程为它做实际服务。
 
并发模型与线程池:
    在典型的并发模型中,服务器为每一个客户端创建一个线程,如果很多客户同时请求,则这些线程都是运行的,那么 CPU就要一个个切换,CPU花费了更多的时间在线程切换,线程确没得到很多CPU时间。到底应该创建多少个线程比较合适呢,微软件帮助文档上讲应该是2*CPU个。但理想条件下最好线程不要切换,而又能象线程池一样,重复利用。I/O完成端口就是使用了线程池。 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值