Windows设备驱动程序StartIO、IRP同步/异步上机小结

程序位置:http://download.csdn.net/detail/dijkstar/8568463

StartIO非常好用,本质上,它把应用层传递进来的读请求、写请求、IOCTL请求放在一个队列里,由WDM内部串行调度,免去了自己去维护这样一个请求队列的复杂麻烦,下面是驱动层里StartIO的内容(特别处理IRP_MJ_READ请求):

#pragma LOCKEDCODE
VOID  HelloDDKStartIO(IN PDEVICE_OBJECT  DeviceObject,IN PIRP  Irp)
{
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
	

	KIRQL oldirql;
	//KdPrint(("Enter HelloDDKStartIO\n"));

	//获取cancel自旋锁
	IoAcquireCancelSpinLock(&oldirql);
	if (Irp!=DeviceObject->CurrentIrp||Irp->Cancel)
	{
		//如果当前有正在处理的IRP,则简单的入队列,并直接返回
		//入队列的工作由系统完成,在StartIO中不用负责
		IoReleaseCancelSpinLock(oldirql);
		KdPrint(("Leave HelloDDKStartIO\n"));
		return;
	}else
	{
		//由于正在处理该IRP,所以不允许调用取消例程
		//因此将此IRP的取消例程设置为NULL
		IoSetCancelRoutine(Irp,NULL);
		IoReleaseCancelSpinLock(oldirql);
	}

	switch(IrpStack->MajorFunction)
	{
		case IRP_MJ_READ:
		{
			unsigned char i=0;
			KEVENT event;
			static unsigned char  k = 0x30;//字符'0'开始

			KeInitializeEvent(&event,NotificationEvent,FALSE);

			//等2秒
			LARGE_INTEGER timeout;
			timeout.QuadPart = -2*1000*1000*10;

			//定义一个3秒的延时,主要是为了模拟该IRP操作需要大概2秒左右时间
			KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout);
			
			//模拟阻塞完毕后,向用户缓冲区拷贝点内容
			ULONG uLen = IrpStack->Parameters.Read.Length;
			k++;
			for(i=0; i<uLen; i++)
			{
				unsigned char tmp= i+k;//0x30, 0x31,....开始拷贝,每次变化
				unsigned char *p=(unsigned char *)Irp->AssociatedIrp.SystemBuffer;
				memcpy(&p[i], &tmp, 1);
			}

			Irp->IoStatus.Status = STATUS_SUCCESS;
			Irp->IoStatus.Information = uLen;	//
			IoCompleteRequest(Irp,IO_NO_INCREMENT);
			break;
		}
		case IRP_MJ_WRITE:
			//写操作,立刻返回
			//..............
			Irp->IoStatus.Status = STATUS_SUCCESS;
			Irp->IoStatus.Information = 0;	//
			IoCompleteRequest(Irp,IO_NO_INCREMENT);
			break;
		case IRP_MJ_DEVICE_CONTROL:
			//..............
			//IOCTL操作,立刻返回
			Irp->IoStatus.Status = STATUS_SUCCESS;
			Irp->IoStatus.Information = 0;	//
			IoCompleteRequest(Irp,IO_NO_INCREMENT);
			break;
	}


	//在队列中读取一个IRP,并进行StartIo
	IoStartNextPacket(DeviceObject,TRUE);

	//KdPrint(("Leave HelloDDKStartIO\n"));
}


为了实现StartIO,拿“IRP_MJ_READ”请求来说,在该派遣处理中,首先要挂起(PENDING)这个读请求,然后将该请求插入系统队列,最后返回一个挂起状态(STATUS_PENDING):

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
	//KdPrint(("Enter HelloDDKRead\n"));

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pDevObj->DeviceExtension;

	//将IRP设置为挂起
	IoMarkIrpPending(pIrp);

	//将IRP插入系统的队列
	IoStartPacket(pDevObj,pIrp,0,OnCancelIRP);

	//KdPrint(("Leave HelloDDKRead\n"));

	//返回pending状态
	return STATUS_PENDING;
}

这样,应用层对应的ReadFile()处理,有两种调用选择,一种是【同步阻塞】调用,一种是【异步立刻返回】调用。

【同步阻塞】调用,就像套接字的默认函数方式,如recvfrom,调用它时,直到它完成,才返回。要达到该目的,在CreateFile时,必须倒数第二参数【不能】带有FILE_FLAG_OVERLAPPED标志;这样的调用方式最简单,一般需要自己在应用层开启一个线程(CreateThread),在线程的while循环里不断的ReadFile(),因为是阻塞调用,该循环不消耗CPU;

【异步立刻返回】调用,调用ReadFile()时,立刻返回,要达到该目的,CreateFile时,必须倒数第二参数带有FILE_FLAG_OVERLAPPED标志;因为立刻返回,所以要自己创建等待事件实现阻塞(用GetOverlappedResult、WaitForMultipleObjects),下面是应用层异步/同步调用实现,先注释的是异步方式,后面注释打开的是同步方式:

typedef struct  
{
	HANDLE	hDevice;
	int		inx;
}MY_TYPE;

UINT WINAPI Thread(LPVOID context)
{
	MY_TYPE *p = (MY_TYPE *)context;

	printf("进入线程: %d\n", p->inx);
	DWORD dwStart = GetTickCount();



	//等待2秒
	OVERLAPPED overlap={0};
	overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	UCHAR buffer[100] = {0}; 
	ULONG ulRead;
	

/*
	//
	//下面为【异步立刻返回】操作
	//		CreateFile时,必须倒数第二参数带有FILE_FLAG_OVERLAPPED!!
	//
	BOOL bRead = ReadFile(p->hDevice, buffer, 10, &ulRead,&overlap);
	if(bRead == FALSE)
	{
		DWORD nRet = GetLastError();
		if( nRet== ERROR_IO_PENDING)//ERROR_IO_PENDING值=997
		{
			//阻塞方式1:推荐使用使用GetOverlappedResult,因为该函数能返回读到的个数
			GetOverlappedResult(p->hDevice, &overlap, &ulRead, TRUE);
			buffer[ulRead] = 0x00;

			//阻塞方式2:
			//WaitForMultipleObjects(overlap.hEvent, INFINITE);

			DWORD dwEnd = GetTickCount();
			printf("离开线程: %d, 耗时:%.1fs, 返回内容:%d, %s\n", p->inx, (dwEnd- dwStart)/1000.0, ulRead, buffer);

			return 0;
		}
		else
			printf("返回真正的错误: %d\n", nRet);
	}
*/



	//
	// 下面为【同步阻塞】操作
	//		CreateFile时,必须倒数第二参数【不能】带有FILE_FLAG_OVERLAPPED!!
	// 
	BOOL bRead = ReadFile(p->hDevice, buffer,10,&ulRead, NULL);
	buffer[ulRead] = 0x00;

	DWORD dwEnd = GetTickCount();
	printf("离开线程: %d, 耗时:%.1fs, 返回内容:%d, %s\n", p->inx, (dwEnd- dwStart)/1000.0, ulRead, buffer);

  
	return 0;
}

应用层启动程序:(特别注意CreateFile时的倒数第二参数的标志设置)

int main()
{
	HANDLE hDevice = 
		CreateFile("\\\\.\\HelloDDK",
					GENERIC_READ | GENERIC_WRITE,
					FILE_SHARE_READ|FILE_SHARE_WRITE,
					NULL,
					OPEN_EXISTING,
					//FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//【异步立刻返回】
					FILE_ATTRIBUTE_NORMAL,//【同步阻塞】
					NULL );

	if (hDevice == INVALID_HANDLE_VALUE)
	{
		printf("Open Device failed!");
		return 1;
	}

	MY_TYPE my[3] = {0};

	HANDLE hThread[3];
	my[0].hDevice = hDevice;
	my[0].inx = 0;
	hThread[0] = (HANDLE) _beginthreadex (NULL,0,Thread,&my[0],0,NULL);

	my[1].hDevice = hDevice;
	my[1].inx = 1;
	hThread[1] = (HANDLE) _beginthreadex (NULL,0,Thread,&my[1],0,NULL);

	my[2].hDevice = hDevice;
	my[2].inx = 2;
	hThread[2] = (HANDLE) _beginthreadex (NULL,0,Thread,&my[2],0,NULL);

	//主线程等待子线程结束
	WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
	
	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);

	return 0;
}



你可能感兴趣的:(Windows设备驱动程序StartIO、IRP同步/异步上机小结)