zoukankan      html  css  js  c++  java
  • 异步设备IO OVERLAPPED结构(设备内核对象 事件内核对象 可提醒IO)

      同步IO是指:线程在发起IO请求后会被挂起IO完成后继续执行

      异步IO是指:线程发起IO请求后并不会挂起而是继续执行。IO完毕后会得到设备驱动程序的通知

      一.异步准备与OVERLAPPED结构

      (1).为了以异步的方式来访问设备必须先调用CreateFile,并在dwFlagsAndAttributes参数中指定FILE_FLAG_OVERLAPPED标志来打开设备。该标志告诉系统要以异步的方式来访问设备。

      为了将I/O请求加入设备驱动程序的队列中必须使用ReadFile和WriteFile函数

      

    HANDLE CreateFile(    
      LPCTSTR lpFileName,                                         // 文件名/设备路径 设备的名称
      DWORD dwDesiredAccess,                                  // 访问方式
      DWORD dwShareMode,                                      // 共享方式
      LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 安全描述符指针  
      DWORD dwCreationDisposition,                          // 创建方式
      DWORD dwFlagsAndAttributes,                          // 文件属性及标志
      HANDLE hTemplateFile                                      // 模板文件的句柄
    );    
    

      当调用ReadFile,WriteFile这两个函数中任何一个时,函数会检查hFile参数标识的设备是否用FILE_FLAG_OVERLAPPED标志打开的如果打开设备时指定了这个标志,那么函数会执行异步设备I/O。

    BOOL WINAPI ReadFile(
    _In_ HANDLE hFile,
    _Out_ LPVOID lpBuffer,
    _In_ DWORD nNumberOfBytesToRead,
    _Out_opt_ LPDWORD lpNumberOfBytesRead,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
    );
    
    
    BOOL WINAPI ReadFile(  
      _In_         HANDLE hFile,  
      _Out_        LPVOID lpBuffer,  
      _In_         DWORD nNumberOfBytesToRead,  
      _Out_opt_    LPDWORD lpNumberOfBytesRead,  
      _Inout_opt_  LPOVERLAPPED lpOverlapped  
    );  
    

      

      (2).再来看OVERLAPPED结构:

      

    typedef struct _OVERLAPPED {
        ULONG_PTR Internal;
        ULONG_PTR InternalHigh;
        union {
            struct {
                DWORD Offset;
                DWORD OffsetHigh;
            } DUMMYSTRUCTNAME;
            PVOID Pointer;
        } DUMMYUNIONNAME;
    
        HANDLE  hEvent;
    } OVERLAPPED, *LPOVERLAPPED;
    

      1.Internal成员:这个成员用来保存已处理的I/O请求的错误码.

         InternalHigh成员:当异步I/O请求完成的时候,这个成员用来保存已传输的字节数

      2..Offset和OffsetHigh成员,构成一个64位的偏移量,它们表示当访问文件的时候应该从哪里开始进行I/O操作。每个文件内核对象都有一个与之相关联的文件指针。在执行异步I/O的时候,系统会忽略文件指针。这是为了避免在对同一个对象进行多个异步调用的时候出现混淆,所有异步I/O请求必须在OVERLAPPED结构中指定起始偏移量。非文件设备会忽略这两个参数,必须将其初始化为0,否则I/O请求会失败。

      (3.)异步设备IO注意事项

        1:异步IO不会按照你的投递顺序来执行,驱动会选择他认为最快的方式来组合这些投递

        2:错误处理,以文件IO为例,当我们投递一个异步ReadFile()时,设备驱动程序可能会以同步方式执行,例如如果设备驱动程序发现要读取的数据在文件缓冲里时,就不会投递这个异步设备IO,而是直接将数据复制进我们的缓冲区

        3.如果IO是同步方式执行,ReadFile()和WriteFile()返回非零值,如果是异步或者出现错误,返回FALSE,调用GetLastError()获得错误码,如果返回的是ERROR_IO_PENDING,那么IO请求已经被成功地加入了队列。

      二.接收IO请求完成的方法  

       Windows提供了4种不同的技术方法来得到I/O完成的通知。

    技术 概要
    通知一个设备内核对象 当一个设备同时有多个IO请求的时候,该方法不适用。允许一个线程发送一个IO请求,另一个线程处理之。
    通知一个事件内核对象 允许一个设备同时有多个IO请求。允许一个线程发送一个IO请求,另一线程处理之。
    警告IO 允许一个设备同时有多个IO请求。必须在同一个线程中发送并处理同一个IO请求。
    IO完成端口 允许一个设备同时有多个IO请求。允许一个线程发送一个IO请求,另一个线程处理之。该方法伸缩性好,而且性能高。

      

      1.触发设备内核对象

      (1)Read/WriteFile在将I/O请求添加到队列之前,会先将对象设为未触发状态。当设备驱动程序完成了请求之后,会将设备内核对象设为触发状态,线程可以通过WaitForSingalObject或WaitForMultiObject来检查一个异步IO请求是否完成。

      (2)不能向同一个设备发出多个IO请求

      2.触发事件内核对象

      (1)在每个I/O请求的OVERLAPPED结构体的hEvent创建一个用来监听该请求完成的事件对象。当一个异步I/O请求完成时设备驱动程序会调用SetEvent来触发事件。驱动程序仍然会像从前一样,将设备对象也设为触发状态,因为己经有了可用的事件对象,所以可以通过SetFileCompletionNoticationModes(hFile,FILE_SKIP_SET_EVENT_ON_HANDLE)来告诉操作系统在操作完成时,不要触发文件对象。

      3.使用可提醒IO 

      (1)创建线程时,会同时创建一个与线程相关联的APC队列(异步过程调用),可以告诉设备程序驱动程序在I/O完成时,为了将通知信息添加到线程的APC队列中,需要调用ReadFileEx和WriteFileEx函数

      (2)ReadFile/WriteFileEx函数与Read/WriteFile最大的不同在于最后一个参数,这是一个回调函数(也叫完成函数)的地址,当*Ex发出一个I/O请求时,这两个函数将回调函数的地址传给设备驱动程序。当设备驱动程序完成I/O请求的时候,会在发出I/O请求的线程的APC队列中添加一项。该项包含了完成函数的地址以及发出I/O请求时使用的OVERLAPPED的地址

      对于这种异步设备I/O方式,确实没什么用。重要的是微软为可提醒IO构建的基础设施——APC(Asynchronous Procedure Call),异步过程调用。

      当创建一个线程的时候,系统会为线程维护一个APC队列,该队列中的项目想要得到执行,线程必须处于可提醒等待状态,即使用SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx等函数,并且把最后一个参数设置为TRUE。当线程处于可提醒等待状态时,线程就会执行APC队列中的APC,之后执行过的APC就会清除队列,再进行下一次执行APC(如果APC队列中还有未执行的APC)。

      在这一小节中,作者的用意就是不要使用可提醒I/O方式进行异步设备I/O——因为可提醒I/O的两大缺陷:回调函数的累赘和线程的无负载均衡机制

    // Overlapped.cpp : 定义控制台应用程序的入口点。
    
    
    #include "stdafx.h"
    #include <windows.h>
    #include <iostream>
    /*
    Internal成员:这个成员用来保存已处理的I/O请求的错误码.
    InternalHigh成员:当异步I/O请求完成的时候,这个成员用来保存已传输的字节数。
    在当初设计OVERLAPPED结构的时候,Microsoft决定不公开Internal和InternalHigh成员(名副其实)。随着时间的推移,Microsoft认识到这些成员包含的信息会对开发人员有用,因此把它们公开了。但是,Microsoft没有改变这些成员的名字,这是因为操作系统的源代码频繁地用到它们,而Microsoft并不想为此修改源代码。
    */
    using namespace std;
    
    #define PAGE_SIZE 0x1000
    void Sub_1();   //ReadFile 异步操作
    void Sub_2();   //ReadFileEx 
    DWORD WINAPI Sub_1ThreadProcedure(LPVOID ParameterData);
    DWORD WINAPI Sub_2ThreadProcedure(LPVOID ParameterData);
    OVERLAPPED __Overlapped = { 0 };
    char __BufferData[4] = {0};
    
    int main()
    {
    
    	//Sub_1();   //触发事件内核对象
    	Sub_2();     //可提醒IO
    }
    void Sub_1()
    {
    	BOOL       IsOk = FALSE;
    	DWORD      ReturnLength = 0;
    	HANDLE FileHandle = CreateFile(L"ReadMe.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE,
    		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    	if (FileHandle == INVALID_HANDLE_VALUE)
    	{
    		int LastError = GetLastError();
    		goto Exit;
    	}
    	//当一个异步IO请求完成的时候,驱动程序检查OVERLAPPED结构的hEvent成员是否为NULL
    	//如果hEvent不为NULL,那么驱动程序会调用SetEvent来触发事件,这时候就是使用事件对象来检查一个设备操作是否完成,而不是等待设备(文件)对象
    	__Overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  //绝对要创建
    	HANDLE ThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Sub_1ThreadProcedure,
    		(LPVOID)FileHandle, 0, NULL);
    
    	if (__BufferData == NULL)
    	{
    		goto Exit;
    	}
    	IsOk = ReadFile(FileHandle, __BufferData, 4, &ReturnLength, &__Overlapped);  //事件必须创建
    	if (IsOk == FALSE)
    	{
    		int LastError = GetLastError();
    
    		
    		if (LastError == ERROR_IO_PENDING)
    		{
    			//成功
    			printf("ERROR_IO_PENDING
    ");  //重叠I/O返回标志
    		}
    	}
    	WaitForSingleObject(ThreadHandle, INFINITE);
    Exit:
    	if (FileHandle != NULL)
    	{
    		CloseHandle(FileHandle);
    		FileHandle = NULL;
    	}
    	printf("
    ");
    	return;
    }
    DWORD WINAPI Sub_1ThreadProcedure(LPVOID ParameterData)
    {
    	HANDLE FileHandle = (HANDLE)ParameterData;
    	BOOL  IsOk = FALSE;
    	DWORD ReturnLength = 0;
    	while (1)
    	{
    		IsOk = WaitForSingleObject(__Overlapped.hEvent, INFINITE);
    		IsOk -= WAIT_OBJECT_0;
    		if (IsOk == 0)
    		{
    			IsOk = GetOverlappedResult(FileHandle, &__Overlapped, &ReturnLength, INFINITE);
    
    			if (IsOk==TRUE)
    			{
    				int i = 0;
    				for (i = 0; i < ReturnLength; i++)
    				{
    					printf("%c", __BufferData[i]);
    				}
    				__Overlapped.Offset += ReturnLength;
    				ReadFile(FileHandle, &__BufferData, 4, &ReturnLength, &__Overlapped);
    			}
    		
    			else
    			{
    				//数据完毕
    				break;
    			}
    			
    		}
    		else
    		{
    			return 0;
    		}
    	}
    
    	return 0;
    }
    
    void Sub_2()
    {
    	BOOL       IsOk = FALSE;
    	HANDLE FileHandle = CreateFile(L"ReadMe.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE,
    		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    	if (FileHandle == INVALID_HANDLE_VALUE)
    	{
    		int LastError = GetLastError();
    		goto Exit;
    	}
    
    
    	//__Overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);   //不能提供该事件
    	HANDLE ThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Sub_2ThreadProcedure,
    		(LPVOID)FileHandle, 0, NULL);
    
    	if (__BufferData == NULL)
    	{
    		goto Exit;
    	}
    	IsOk = ReadFileEx(FileHandle, __BufferData, 4, &__Overlapped, NULL);
    	if (IsOk == FALSE)
    	{
    		int LastError = GetLastError();
    
    		if (LastError == ERROR_IO_PENDING)
    		{
    			//成功
    			printf("ERROR_IO_PENDING
    ");  //重叠I/O返回标志
    		}
    	}
    	WaitForSingleObject(ThreadHandle, INFINITE);
    Exit:
    	if (FileHandle != NULL)
    	{
    		CloseHandle(FileHandle);
    		FileHandle = NULL;
    	}
    	printf("
    ");
    	return;
    }
    
    DWORD WINAPI Sub_2ThreadProcedure(LPVOID ParameterData)
    {
    	HANDLE FileHandle = (HANDLE)ParameterData;
    	DWORD ReturnLength = 0;
    	BOOL  IsOk = FALSE;
    	while (1)
    	{
    		IsOk = GetOverlappedResult(FileHandle, &__Overlapped, &ReturnLength, TRUE);
    		//当一个可提醒IO完成时,设备驱动程序不会试图去触发一个事件对象
    		//IsOk = WaitForSingleObject(__Overlapped.hEvent, INFINITE);   
    		if (IsOk == TRUE)
    		{
    			int i = 0;
    			for (i = 0; i < ReturnLength; i++)
    			{
    				printf("%c", __BufferData[i]);
    			}
    
    			__Overlapped.Offset += ReturnLength;
    			ReadFileEx(FileHandle, &__BufferData, 4, &__Overlapped, NULL);
    		}
    		else
    		{
    			return 0;
    		}
    	}
    
    	return 0;
    }
    

      

        

    // Overlapped.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <windows.h>
    #include <iostream>
    using namespace std;
    VOID CALLBACK CompletionRoutine(
    	_In_    DWORD        ErrorCode,
    	_In_    DWORD        ReturnLength,
    	_Inout_ LPOVERLAPPED Overlapped);
    HANDLE __FileHandle = NULL;
    char __BufferData[20] = {0};
    int main()
    {
    	BOOL       IsOk = FALSE;
    	OVERLAPPED Overlapped = { 0 };
    	__FileHandle = CreateFile(L"ReadMe.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE,
    		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, NULL);
    	if (__FileHandle == INVALID_HANDLE_VALUE)
    	{
    		int LastError = GetLastError();
    		goto Exit;
    	}
    	IsOk = ReadFileEx(__FileHandle, __BufferData, 4,&Overlapped,
    		(LPOVERLAPPED_COMPLETION_ROUTINE)CompletionRoutine);
    	if (IsOk == FALSE)
    	{
    		int LastError = GetLastError();
    		if (LastError == ERROR_IO_PENDING)
    		{
    			//成功
    		}
    	}
    Exit:
    	SleepEx(0,TRUE);
    	if (__FileHandle != NULL)
    	{
    		CloseHandle(__FileHandle);
    		__FileHandle = NULL;
    	}	
    	printf("Input AnyKey To Exit
    ");
    	getchar();
    	return 0;
    }
    VOID CALLBACK CompletionRoutine(
    	_In_    DWORD        ErrorCode,
    	_In_    DWORD        ReturnLength,
    	_Inout_ LPOVERLAPPED Overlapped
    )
    {
    	if (ErrorCode == ERROR_SUCCESS)
    	{
    		int i = 0;
    		for (i = 0; i < ReturnLength; i++)
    		{
    			printf("%c", __BufferData[i]);
    		}
    
    		Overlapped->Offset += ReturnLength;
    		ReadFileEx(__FileHandle, __BufferData, 4, Overlapped,
    			(LPOVERLAPPED_COMPLETION_ROUTINE)CompletionRoutine);
    	}
    	else if (ErrorCode==ERROR_HANDLE_EOF)
    	{
    		//数据完成
    		printf("
    ");
    	}
    	else
    	{
    		
    	}
    }
    

      

  • 相关阅读:
    第二章 数据类型、变量、和运算符
    第一章
    ActiveMQ点对点的发送和接收
    初探设计模式(1)——工厂模式
    IDEA使用switch传入String编译不通过
    MyBatis日期转换报错
    HTML页面传值问题
    maven配置本地仓库
    Maven的下载及安装
    PHP实现获得一段时间内所在的所有周的时间
  • 原文地址:https://www.cnblogs.com/lsh123/p/7385317.html
Copyright © 2011-2022 走看看