windows 核心编程之 10 同步设备IO与异步设备IO

 写读书笔记的目的是加强理解,记录自己学习的过程
在microsoft Windows 应用程序中,线程是我们最好的工具,可以用来对工作进行划分。
为了不让线程闲下来,我们需要让各个线程就他们正在执行的操作相互通信。有一种非常好的机制来进行这类通信。 它就叫IO完成端口,它可以帮助我们创建高性能而且伸缩性好的应用程序。通过使用IO完成端口,我们可以让线程在读取设备和写入设备的时候不必等待设备的响应,从而显著的提高吞吐量。

作为一名Windows开发人员,必须完全理解IO完成端口的工作方式,IO完成端口可以与设备有关系,也可以与设备无关,它是一种有无数种用途的绝佳的线程间通信机制。

1  打开和关闭设备
Windows 的优势之一是它支持的设备数量,就是我们与之能够通信的任何东西.


用来打开各种设备的函数如下:



一般的设备句柄使用closehandle来关闭,套接字使用colosesocket.

GetFileType来查询设备的类型,函数返回下列值.
windows 核心编程之 10 同步设备IO与异步设备IO_第1张图片



细看CrateFile函数
HANDLE CreateFile
 LPCTSTRlpFileName, 文件名
 DWORDdwDesiredAccess, 访问模式
 DWORDdwShareMode,  共享模式
 LPSECURITY_ATTRIBUTESlpSecurityAttributes, 安全属性
 DWORDdwCreationDisposition, 创建标志
 DWORDdwFlagsAndAttributes,  读取标志位和文件属性  非常大的文件这个标志位 FILE_FLAG_NO_BUFFERING
 HANDLEhTemplateFile); 模板文件

函数成功返回文件句柄,失败时返回invalid_handle_value
Handle  hFile = CreateFile(...);
if( invalid_handle_value == hFile)
{
打开文件句柄失败.
}
else
{
打开成功.
}
2 使用文件设备
GetfileSize(handle,pDWORD)  获得文件大小,当文件大于4GB时,Pword参数表示高位,返回值表上地位. 
GetCompressedFileSize 返回物理文件大小

设置文件指针的位置

BOOL SetFilePointerEx
 HANDLEhFile,  文件句柄对象
 LARGE_INTEGERliDistanceToMove, 需要移动的字节数
 PLARGE_INTEGERlpNewFilePointer, 更新后的文件指针位置
 DWORDdwMoveMethod); FILE_BEGIN ,FILE_CURRENT , FILE_END

设置文件尾部
强制文件变小或者变大
DWORD dSize,dsize1;
LARGE_INTEGER LagreSize;
LagreSize.QuadPart = 1024;
SetFilePointerEx(hFile, LagreSize, NULL, FILE_BEGIN);
SetEndOfFile(hFile);
CloseHandle(hFile);

3 执行同步设备IO

最常用的对设备数据的读取的函数是Readfile,WriteFile函数

将数据刷新至设备
FlushFileBuffers(HANDLE hfile);//把所以缓冲区的数据刷新到指定文件句柄

同步IO比如,Createfile函数会阻塞其他调用线程。必须等待函数返回才可以继续执行.

在WindowsVista中
CancelSynchronousIo(HANDLE hThread);
取消指定线程没有完成的同步IO请求
hThread 线程句柄必须要有 THREAD_TERMINATE权限

返回非0表示函数成功,返回0表示失败,更多信息见MSDN

4 异步设备IO基础
把异步IO请求加入队列是开发高性能和可伸缩高的应用程序的本质所在。

为了以异步方式访问设备,在CreatFile时 dwFlagsAndAttributes标示必须指定FILE_FLAG_OVERLAPPED 异步重叠IO。 表示我们想要用异步方式来访问设备。

OVERLAPPED 结构 

The OVERLAPPED structure contains information used in asynchronous (or overlapped) input and output (I/O). 用于异步通信的一个结构体信息.

typedef struct _OVERLAPPED {
  ULONG_PTR Internal;   由驱动程序设置 这个成员用来保存已处理的IO请求的错误码
  ULONG_PTR InternalHigh;
由驱动程序设置 表示IO完成时已经传输的字节数

union { struct { DWORD Offset; 如果设备是文件时需要设置读取偏移量,低32位 如果不是文件时这2个参数都设置为0 DWORD OffsetHigh;
如果设备是文件时需要设置读取偏移量,高32位 大于4GB文件需要设置

}; PVOID Pointer; 系统保留 }; HANDLE hEvent; 事件对象,当Io完成时,它变成有信号状态
} OVERLAPPED, 
*LPOVERLAPPED;
 
  
HasOverlappedIoCompleted宏使用的就是第一个参数, 函数返回true表示IO请求完成,false表示IO请求没有完成.
 
  
异步设备IO的注意事项
1. 异步IO的请求不是先进先出,也就是说设备驱动根据效率自己选择执行哪一个IO请求。
1ReadFile 
2WriteFile 先调用读文件,在调用写文件,他们都是异步请求的,那么有可能先写文件,在读文件。
2. 如何正确的来检查错误 如果请求的IO异步方式执行,那么Readfile或者WriteFile返回 false,那么需要调用GetLastError 来获得信息,如果返回的是 ERROR_IO_PENDING 表示IO请求已经加入队列,晚些时候完成.
3. 异步IO完成前,一定不能销毁,或者移动数据缓存和overlapped结构体信息.
必须为每个IO请求初始化和分配一个不同的overlapped结构体信息
void ReadData(handle hfile)
{
 overlapped ol = {0};
byte b[100];
  reatefile(hfile, b, 100, NULL, &ol);
}
看上去这段代码没有问题,但其实有很大隐患。异步IO请求加入队列以后,函数返回了,局部变量释放。处理IO请求的驱动程序并不知道,它会去修改那个地址,有可能引起崩溃。如果设备驱动以同步方式处理可能没有问题,但是以异步方式处理时,就修改了内存内容,并且很难发现。
取消列队中的设备IO请求
1 CancelIO(handle hFile)
2 关闭设备句柄来取消所有添加到列表的请求,不管是哪个线程添加的
3 线程终止时,系统自动取消该线程添加的所有IO请求。
4 给定文件句柄的一个指定的IO请求关闭时,调用CancleioEx(HANDLE hfile, LPOVERLAPPED lOverlapped);
如果loverlapped不为NULL那么取消一个特定的IO请求,如果为NULL取消所有句柄为HFile的IO请求。
5 接收IO请求完成通知
Windows 提供了4种不同的方法来通知IO请求已经完成的通知. 下列表格表示从容易到困难的
windows 核心编程之 10 同步设备IO与异步设备IO_第2张图片
IO完成端口无疑是最好的。
1 触发设备内核对象 没有多大用处,只能处理一个IO请求
代码如下:
//异步方式打开一个文件句柄
	HANDLE hFile = CreateFile(L"sn_new.txt",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		printf("file open faild\n");

		return 0;
	}
	OVERLAPPED overlapped = {0};
	overlapped.Offset = 100; //表示从100的位置开始读取 大于4GB文件才需要设置 OffsetHigh
	byte buffer[100]= {0};
	
	bool bReadDone = ReadFile(hFile, buffer, 100, NULL, &overlapped);
	if (!bReadDone && ERROR_IO_PENDING == GetLastError())
	{
		//表示IO请求已经加入队列,稍候完成
		bool bStop = false;

		while(!bStop)
		{
			//表示设备内核触发了
			DWORD dRes = WaitForSingleObject(hFile, 1000);//等待1秒

			switch(dRes)
			{
			case WAIT_OBJECT_0:
				//表示IO处理完毕,buffer有数据了
				bStop = true;
				break;
			case WAIT_TIMEOUT:
				//表示IO没有处理完毕
				break;
			default: WAIT_FAILED:
				break;
				
			}
			Sleep(2000);//休息2秒
		}
	}
	else
	{
		//表示读取文件错误,更多信息见GetLastError();
	}

2 触发事件内核对象 比设备内核对象好用,可以处理多个IO请求
下面的代码是内核对象处理IO请求的局限性

//异步方式打开一个文件句柄
	HANDLE hFile = CreateFile(L"sn_new.txt",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);
	OVERLAPPED Readoverlapped = {0};
	Readoverlapped.Offset = 100; //表示从100的位置开始读取 大于4GB文件才需要设置 OffsetHigh
	byte buffer[100]= {0};
	
	bool bReadDone = ReadFile(hFile, buffer, 100, NULL, &Readoverlapped);

	OVERLAPPED WriteOverlapped = {0};
	WriteOverlapped.Offset = 0; //表示从文件开始的位置写
	byte writeBuffer[100]={"dfffffffffffff"};
	bool bWirteDone = WriteFile(hFile, writeBuffer,100,NULL,&WriteOverlapped);

	WaitForSingleObject(hFile, INFINITE);// 如果等待文件句柄有信息了,那么是读完成了,还是写完成了,无法区别

所以事件内核对象出场了.

 OVERLAPPED 结构的最后一个成员变量 hevent 表示一个事件,利用CreateEvent函数来创建一个事件
当IO请求处理完毕时,设备驱动程序把这个事件设置为有信号状态
那么我们应该只等待事件对象,不在应该等待设备内核对象了。
关闭触发文件对象有一个函数
SetFileCompletionNotificationModes(HANDLE hfile, UCHAR flags = FILE_SKIP_SET_EVENT_ON_HANDLE);
表示这个文件内核对象有IO完成时,也不会触发

下面的代码介绍了怎么使用事件对象:
//下面使用事件内核对象来处理IO完成请求

	//异步方式打开一个文件句柄
	HANDLE hFile = CreateFile(L"sn_new.txt",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);
	OVERLAPPED Readoverlapped = {0};
	Readoverlapped.Offset = 100; //表示从100的位置开始读取 大于4GB文件才需要设置 OffsetHigh
	Readoverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE,NULL);//默认安全属性,自动事件,未触发,匿名
	byte buffer[100]= {0};
	
	bool bReadDone = ReadFile(hFile, buffer, 100, NULL, &Readoverlapped);

	OVERLAPPED WriteOverlapped = {0};
	WriteOverlapped.Offset = 0; //表示从文件开始的位置写
	WriteOverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE,NULL);
	byte writeBuffer[100]={"dfffffffffffff"};//默认安全属性,自动事件,未触发,匿名

	bool bWirteDone = WriteFile(hFile, writeBuffer,100,NULL,&WriteOverlapped);

	HANDLE hEventArray[2];
	hEventArray[0] = Readoverlapped.hEvent;
	hEventArray[1] = WriteOverlapped.hEvent;

	DWORD drs = WaitForMultipleObjects(_countof(hEventArray), hEventArray, FALSE, 2000);
	switch(drs)
	{
	case WAIT_OBJECT_0:
		//读完成
		break;
	case WAIT_OBJECT_0 + 1:
		//写完成
		break;
	case WAIT_TIMEOUT:
		//读和写都没有完成 去泡妞吧
		break;
	case WAIT_FAILED:
		//句柄无效
		break;
	}
	

检查重叠IO的结果

BOOL GetOverlappedResult( HANDLE hFile , LPOVERLAPPED lpOverlapped , LPDWORD lpNumberOfBytesTransferred , BOOL bWait );
参数1 表示设备句柄
参数2 表示 重叠IO结构体
参数3 表示 返回的字节数
参数4 表示 是否一直等待直到返回结果为止 


3  可提醒IO
一般不推荐使用,请注意它的一些基础设施。
允许我们手动添加一项到ACP
QueueUserAPC(
    __in PAPCFUNC pfnAPC, 函数指针
    __in HANDLE hThread,  线程ID
    __in ULONG_PTR dwData 回调函数的值
    );
如果线程创建的时候没有运行,那么如果有ACP请求,会先处理APC,然后才开始运行线程,并且一起处理所以的APC
即使线程挂起,也会把ACP请求添加到ACP队列中去,如果使用了 可提醒函数等待的话,那么每次函数执行时都会把所以的ACP列队中的请求处理完,才返回,返回值为WAIT_IO_COMPLETION,如果下次又有新的请求,那么继续要调用可提醒函数,才能处理列队中的请求。

IO完成端口另外发一篇文章单独详细说明

你可能感兴趣的:(编程,C++,windows,读书笔记)