zcc1414

博客园 首页 联系 订阅 管理

对设备的的操作转换为IRP请求,而一般IRP都是由操作系统异步发送的。

异步处理IRP有助于提高效率,但有时会导致逻辑错误,需要将异步的IRP进行同步化

StartIOl例程,使用中断服务例程等。


应用程序对设备的同步+异步操作

大部分IRP是由应用=程序的WIN32 API 函数发起的。这些API  EG:  ReadFile,WriteFile,DeviceIOControl 等  都有同步异步操作

同步操作(等待继续·····)

HANDLE CreateFile(
  LPCTSTR lpFileName,                         // file name
  DWORD dwDesiredAccess,                      // access mode
  DWORD dwShareMode,                          // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
  DWORD dwCreationDisposition,                // how to create
  DWORD dwFlagsAndAttributes,                 // file attributes ////////下面那个参数///////////////////////
  HANDLE hTemplateFile                        // handle to template file
);
FILE_FLAG_OVERLAPPED为异步标志

BOOL ReadFile(
  HANDLE hFile,                // handle to file
  LPVOID lpBuffer,             // data buffer
  DWORD nNumberOfBytesToRead,  // number of bytes to read
  LPDWORD lpNumberOfBytesRead, // number of bytes read
  LPOVERLAPPED lpOverlapped    // overlapped buffer
);
If hFile was not opened with FILE_FLAG_OVERLAPPED and lpOverlapped is NULL  同步操作

演示代码:

	HANDLE hDevice = 
		CreateFile("test.dat",
					GENERIC_READ | GENERIC_WRITE,
					0,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL,//此处没有设置FILE_FLAG_OVERLAPPED
					NULL );

	if (hDevice == INVALID_HANDLE_VALUE) 
	{
		printf("Read Error\n");
		return 1;
	}

	UCHAR buffer[BUFFER_SIZE];
	DWORD dwRead;
	ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,NULL);//这里没有设置OVERLAP参数

	CloseHandle(hDevice);

异步操作设备1(不等待  继续运行·····)

typedef struct _OVERLAPPED { 
    ULONG_PTR  Internal; 
    ULONG_PTR  InternalHigh; 
    DWORD  Offset;            //指定一个偏移量,设备的偏移量开始读取  64位整形表示,参数是该参数的32位整形
    DWORD  OffsetHigh;        //偏移量的高32位
    HANDLE hEvent;            //这个事件用于该操作完成后通知应用程序,可以初始化改事件为未激发,当操作设备结束后,驱动调用IoCompleteRequest后,设置该设备为激发态
} OVERLAPPED; 
演示代码:
	HANDLE hDevice = 
		CreateFile("test.dat",
					GENERIC_READ | GENERIC_WRITE,
					0,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
					NULL );

	if (hDevice == INVALID_HANDLE_VALUE) 
	{
		printf("Read Error\n");
		return 1;
	}

	UCHAR buffer[BUFFER_SIZE];
	DWORD dwRead;

	//初始化overlap使其内部全部为零
	OVERLAPPED overlap={0};

	//创建overlap事件   自动事件
	overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

	//这里没有设置OVERLAP参数,因此是异步操作
	ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);

	//做一些其他操作,这些操作会与读设备并行执行

	//等待读设备结束
	WaitForSingleObject(overlap.hEvent,INFINITE);

异步操作设备2(不等待  继续运行·····)
除了 ReadFile  WriteFile 外,还有 ReadFileEx  +  WriteFileEx 函数

不同的是  Ex只支持 异步读写操作的

BOOL ReadFileEx(
  HANDLE hFile,                                       // handle to file
  LPVOID lpBuffer,                                    // data buffer
  DWORD nNumberOfBytesToRead,                         // number of bytes to read
  LPOVERLAPPED lpOverlapped,                          // offset                   //指针   !!不需要提供事件句柄
  LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine       //完成例程
);
驱动程序在读操作后,会通过调用 上面那个回调例程。   类似一个软中断  

WINDOWS将这种机制称为 异步过程调用 APC asynchronousProcedure

回调函数被调用条件   只有先成功处于警惕(alert) 状态,回调函数才有可能被调用。有多个API可以使系统进程警惕状态  SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectsEx函数等

当系统进入警惕模式后,操作系统会枚举当前线程的APC队列。驱动程序一旦结束读写操作,就会把ReadFileEx提供的的完成例程插入到APC队列

回调例程会报告本次操作的完成状况 +  本次读取操作实际读取的字节数等。

VOID CALLBACK FileIOCompletionRoutine(                             //一般回调例程的声明
  DWORD dwErrorCode,                // completion code             //读取错误,会返回错误码
  DWORD dwNumberOfBytesTransfered,  // number of bytes transferred //返回实际读取操作的字节数
  LPOVERLAPPED lpOverlapped         // I/O information buffer      //OVERLAP参数,指定读取的偏移量等信息
);
下面是演示代码:

#define DEVICE_NAME	"test.dat"
#define BUFFER_SIZE	512
//假设该文件大于或等于BUFFER_SIZE
VOID CALLBACK MyFileIOCompletionRoutine(
  DWORD dwErrorCode,                // 对于此次操作返回的状态
  DWORD dwNumberOfBytesTransfered,  // 告诉已经操作了多少字节,也就是在IRP里的Infomation
  LPOVERLAPPED lpOverlapped         // 这个数据结构
)
{
	printf("IO operation end!\n");
}
	HANDLE hDevice = 
		CreateFile("test.dat",
					GENERIC_READ | GENERIC_WRITE,
					0,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
					NULL );
	if (hDevice == INVALID_HANDLE_VALUE) 
	{
		printf("Read Error\n");
		return 1;
	}
	UCHAR buffer[BUFFER_SIZE];
	//初始化overlap使其内部全部为零
	//不用初始化事件!!
	OVERLAPPED overlap={0};
	//这里设置了OVERLAP参数,因此是异步操作
	ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);

	//做一些其他操作,这些操作会与读设备并行执行
	//进入alterable
	SleepEx(0,TRUE);
	CloseHandle(hDevice);

IRP的同步完成 + 异步完成

上面的这些操作必须得到驱动程序的支持,有两种方式处理IRP请求:

1)在派遣函数中直接结束IRP请求,可以认为是一种同步处理

2)不结束IRP请求,让派遣函数直接返回, IRP在以后的某个时候再进行处理。

对设备读取3个方法:

1)ReadFIle 如果同步读取时,创建一个事件(IRP 的 UserEvent),派遣函数调用IoCompleteRequest时内部会设置IRP的UserEvent

如果派遣函数没有调用IoCompleteRequest函数,那么ReadFile一直等待


2)ReadFile  异步处理,内部不会创建事件,但ReadFile函数会接受 overlap参数。参数会提供一个事件,被用作 同步处理

IoCompleteRequest内部设置overlap 提供的事件

在ReadFile函数退出之前不会检测该事件是否被设置,因此可以不等待操作是否真的被完成

当IRP操作被完成后,overlap提供的事件被设置,通知应用程序IRP请求被完成


3)ReadFileEx 异步读取时,不提供事件,但提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给派遣函数

IoCompleteRequest会将这个完成函数插入APC队列

应用程序只要进入警惕模式,APC队列会自动出队列,完成函数会被执行,这相当于通知应用程序操作已经完成


前几次所有例子都是  同步读取,IRP的同步处理在派遣函数中,将IRP处理完毕,这里指的完毕就是调用IoCompleteRequrst函数

IRP的异步完成:

指的就是不在派遣函数中调用IoCompleteRequest内核函数,调用IoCompleteRquest函数意味着IRP请求的结束,也标志着本次对设备操作的结束

借用上面的例子~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1)没有调用IoCompleteRequest,IRP请求没有被结束。ReadFIle会一直等待,知道操作被结束

2)没有调用IoCompleteRequest,IRP请求没有被结束。ReadFile会立刻返回失败,GetLastError函数得知 ERROR_IO_PENDING 不是真正的操作错误

3)没有调用IoCompleteRequest,IRP请求没有被结束。立刻返回FALSE,跟(2)一样,表明当前操作被“挂起”。


下面介绍如何将“挂起”的IRP插入队列,并在关闭设备的时候将“挂起”的IRP结束:

如果不调用 IoC~R~函数,则需要告诉操作系统处于“挂起”状态。

VOID 
  IoMarkIrpPending(   //告诉操作系统处于挂起状态 同时返回STATUS_PENDING
    IN OUT PIRP  Irp
    );

NTSTATUS HelloDDKRead(IN PDEV0CE_OBJECT pDevObj,  
                                 IN PIRP pIrp)   
{
	IoMarkIrkPending(pIrp);
	return   STATUS_PENDING;
}
队列的数据结构:

typedef struct _MY_IRP_ENTRY
{
	PIRP pIrp;				//记录IRP指针
	LIST_ENTRY ListEntry;
}MY_IRP_ENTRY,*PMY_IRP_ENTRY;
在设备扩展力加入  “队列” 这个变量。所有派遣函数都可以使用这个队列

DriverEntry中初始化 该队列    DriverUnload回收该队列

在IRP_MJ_READ的派遣函数中,将IRP插入堆栈,然后返回“挂起”状态:

异步处理IRP的派遣函数:

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

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pDevObj->DeviceExtension;

	PMY_IRP_ENTRY pIrp_entry = (PMY_IRP_ENTRY)ExAllocatePool(PagedPool,sizeof(MY_IRP_ENTRY));

	pIrp_entry->pIRP = pIrp;

	//插入队列
	InsertHeadList(pDevExt->pIRPLinkListHead,&pIrp_entry->ListEntry);

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

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

	//返回pending状态
	return STATUS_PENDING;
}

在关闭设备时,产生IRP_MJ_CLEANUP 的IRP,其派遣函数抽取队列中每一个“挂起”   的IRP,并调用IoCompleteRequest设置完成

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

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pDevObj->DeviceExtension;
	
	//(1)将存在队列中的IRP逐个出队列,并处理

	PMY_IRP_ENTRY my_irp_entry;
	while(!IsListEmpty(pDevExt->pIRPLinkListHead))
	{
		PLIST_ENTRY pEntry = RemoveHeadList(pDevExt->pIRPLinkListHead);
		my_irp_entry = CONTAINING_RECORD(pEntry,
                              MY_IRP_ENTRY,
                              ListEntry);
 		my_irp_entry->pIRP->IoStatus.Status = STATUS_SUCCESS;
 		my_irp_entry->pIRP->IoStatus.Information = 0;	// bytes xfered
 		IoCompleteRequest( my_irp_entry->pIRP, IO_NO_INCREMENT );
 
		ExFreePool(my_irp_entry);
	}
	
	//(2)处理IRP_MJ_CLEANUP的IRP
	NTSTATUS status = STATUS_SUCCESS;
	// 完成IRP
	pIrp->IoStatus.Status = status;
	pIrp->IoStatus.Information = 0;	// bytes xfered
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );

	KdPrint(("Leave HelloDDKCleanUp\n"));
	return STATUS_SUCCESS;
}

应用程序代码:

	HANDLE hDevice = 
		CreateFile("\\\\.\\HelloDDK",
					GENERIC_READ | GENERIC_WRITE,
					0,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
					NULL );

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

	OVERLAPPED overlap1={0};
	OVERLAPPED overlap2={0};

	UCHAR buffer[10];
	ULONG ulRead;
	
	BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1);
	if (!bRead && GetLastError()==ERROR_IO_PENDING)
	{
		printf("The operation is pending\n");
	}
	bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);
	if (!bRead && GetLastError()==ERROR_IO_PENDING)
	{
		printf("The operation is pending\n");
	}
	
	//迫使程序中止2秒
	Sleep(2000);

	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);

所以 当应用程序  异步读设备时,创建两个IRP_MJ_READ,这两个IRP被插入队列,显示  Pending

停顿2秒           关闭设备的时候,导致驱动程序调用IRP_MG_CLEANUP的派遣函数  显示 Completed


 



下面介绍将“挂起”的IRP逐个结束,取消IRP请求:

PDRIVER_CANCEL 
  IoSetCancelRoutine(                 //可以设置取消IRP请求的回调函数
    IN PIRP  Irp,                     //需要取消的IRP请求的回调函数
    IN PDRIVER_CANCEL  CancelRoutine  //取消函数的函数指针,一旦IRP请求被取消的时候,操作系统会调用这个取消函数
    );
可以将一个取消例程与该IRP关联,一旦取消IRP请求的时候,这个取消例程会被执行

这个函数也可以用来删除取消例程,当输入的  第二指针为空, 则删除原来设置的取消例程

BOOLEAN 
  IoCancelIrp(    //指针取消IRP请求,zai IoCancelIrp内部,需要进行同步。 DDK在IoCancelIrp内部使用一个叫做cancel的自旋锁用来进行同步
    IN PIRP  Irp
    );

在内部首先得到该自旋锁,IoCancelIrp会调用 取消回调 例程,因此,释放该自旋锁的任务就留给了取消回调例程。

VOID 
  IoAcquireCancelSpinLock( //获得取消自旋锁
    OUT PKIRQL  Irql
    );
VOID 
  IoReleaseCancelSpinLock( //释放取消自旋锁
    IN KIRQL  Irql
    );
BOOL CancelIo(
  HANDLE hFile  // handle to file // 这个是WIN32 API 取消IRP请求 内部会调用所有没有被完成的IRP,然后依次调用IoCancelIrp.如果没有调用它,                                       在关闭设备同意自动调用CancelIo
);

应用程序代码:

	HANDLE hDevice = 
		CreateFile("\\\\.\\HelloDDK",
					GENERIC_READ | GENERIC_WRITE,
					0,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
					//此处设置FILE_FLAG_OVERLAPPED
					NULL );

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

	OVERLAPPED overlap1={0};
	OVERLAPPED overlap2={0};

	UCHAR buffer[10];
	ULONG ulRead;
	
	BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1);
	if (!bRead && GetLastError()==ERROR_IO_PENDING)
	{
		printf("The operation is pending\n");
	}
	bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);
	if (!bRead && GetLastError()==ERROR_IO_PENDING)
	{
		printf("The operation is pending\n");
	}
	
	//迫使程序中止2秒
	Sleep(2000);

	//显式的调用CancelIo,其实在关闭设备时会自动运行CancelIo
	CancelIo(hDevice);

	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);
驱动代码(省略):

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

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pDevObj->DeviceExtension;

	IoSetCancelRoutine(pIrp,CancelReadIRP); //设置取消IRP请求的回调函数

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

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

	//返回pending状态
	return STATUS_PENDING;
}
VOID
CancelReadIRP(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
	KdPrint(("Enter CancelReadIRP\n"));

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			DeviceObject->DeviceExtension;
	
	//设置完成状态为STATUS_CANCELLED
 	Irp->IoStatus.Status = STATUS_CANCELLED;
 	Irp->IoStatus.Information = 0;	// bytes xfered
 	IoCompleteRequest( Irp, IO_NO_INCREMENT );

	//释放Cancel自旋锁
	IoReleaseCancelSpinLock(Irp->CancelIrql); //一定要释放cancel自旋锁!!否则系统崩溃,另外cancel自旋锁是全局自旋锁,                                                           所有驱动程序都会使用这个自旋锁,因此自旋锁时间不宜过长
	KdPrint(("Leave CancelReadIRP\n"));
}
运行结果相似

























posted on 2013-09-21 23:45  zcc1414  阅读(425)  评论(0编辑  收藏  举报