IRP的同步问题

2023-11-17

转载自:   http://zhan.renren.com/debugman?tagId=178558&page=2&checked=true

 

一、前言

对设备的任何操作都会最终转化为IRP请求,而IRP一般都是由操作系统异步发送的。异步处理IRP有助于提高效率,但是有时异步处理会带来逻辑上的错误,这时需要将异步的IRP同步化。将IRP同步化的方法有StartIO例程,使用中断服务例程等。

 

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

大部分IRP都是由应用程序的Win32 API函数发起的。这些Win32 API本身就支持同步和异步的操作。例如:ReadFile,WriteFile和DeviceIoControl等,这些都有两种操作方式。一种同步,一种异步。

 

1.同步操作和异步操作的原理

操作设备的Win32 API主要是三个函数,即ReadFile函数,WriteFile函数,DeviceIOControl函数。以DeviceIOControl函数为例,当应用程序调用DeviceIoControl函数时,它的内部会创建一个IRP_MJ_DEVICE_CONTROL类型的IRP,并将这个IRP传送到驱动程序的派遣函数中。处理该IRP需要一段时间,直到IRP处理完毕后,DeviceIOControl函数才会返回。

同步操作时在DeviceIOControl的内部,会调用WaitForSingleObject函数去等待一个事件。这个事件直到IRP被结束时,才会被触发。如果通过反汇编IoCompleteRequest内核函数,就会发现IoComplpeteRequest内部设置了该事件。DeviceIOControl会暂时进入睡眠状态,直到IRP被结束。

而对于异步操作的情况下,当DeviceIOControl被调用时,其内部会产生IRP,并将IRP传递给驱动程序的内部派遣函数。但此时DeviceIOControl不会等待该IRP的结束,而是直接返回。当IRP经过一段时间被结束时,操作系统会触发一个IRP相关事件。这个事件可以通知应用程序IRP请求被执行完毕。

 

 

2.同步操作设备

如果需要同步操作设备,在打开设备的时候就要制定以“同步”的方式打开设备。打开设备用CreateFile函数,其函数声明如下:

 

HANDLE  CreateFile(
LPCSTR lpFileName,							//设备名		
DWORD dwDesiredAccess,                      //访问权限		
DWORD dwShareMode,                          //共享模式		
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全属性		
DWORD dwCreationDisposition,                //如何创建		
DWORD dwFlagsAndAttributes,                 //设备属性		
HANDLE hTemplateFile                        //文件模板	
);


其中第六个参数dwFlagsAndAttributes是同步异步操作的关键。如果这个参数没有设置FILE_FLAG_OVERLAPPED,则以后对该设备的操作都是同步操作,否则都是异步操作。

对设备的操作Win32 API,例如ReadFile,WriteFile和DeviceIOControl函数,都会提供一个OVERLAP参数,如ReadFile函数:

BOOL ReadFile(	
HANDLE hFile,	
LPVOID lpBuffer,	
DWORD nNumberOfBytesToRead, 
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped 
);


在同步操作设备时,其lpOverlapped参数设置为NULL。

 

3.异步操作设备(方式一)

异步操作设备时主要需要OVERLAP参数,Windows中用一种数据结构OVERLAPPED表示。

typedef struct _OVERLAPPED {
    ULONG_PTR Internal;
    ULONG_PTR InternalHigh;
    union {
        struct {
            DWORD Offset;
            DWORD OffsetHigh;
        };

        PVOID Pointer;
    };

    HANDLE  hEvent;
} OVERLAPPED, *LPOVERLAPPED;


 

 

 

第三个参数Offset:操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量用一个64位整型表示,Offset就是偏移量的低32位。

第四个参数OffsetHigh是偏移量的高32位。

第五个参数hEvent:这个事件用于该操作完成后通知应用程序。程序员可以初始化该事件为未激发,当操作设备结束后,即在驱动程序中调用IoCompleteRequest后,设置该事件为激发态。

在使用OVERLAPPED结构前,要先对其内部清零,并为其创建事件。

下面代码演示如何在应用程序中使用异步操作:

 

#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE	512//假设该文件大于或等于BUFFER_SIZE
#define DEVICE_NAME	"test.dat"
int main()
{	
	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);	
	CloseHandle(hDevice);	
	return 0;
}


 

4.异步操作设备(方式二)

除了ReadFile和WriteFile函数外,还有两个API也可以实现异步读写,这就是ReadFileEx和WriteFileEx函数。ReadFile和WriteFile既可以支持同步读写操作,又可以支持异步读写操作。而ReadFileEx和WriteFileEx函数时专门用于异步操作的,不能进行同步读写。ReadFileEx的声明如下:

WINBASEAPI
BOOL
WINAPI
ReadFileEx(
    __in     HANDLE hFile,
    __out_bcount_opt(nNumberOfBytesToRead) __out_data_source(FILE) LPVOID lpBuffer,
    __in     DWORD nNumberOfBytesToRead,
    __inout  LPOVERLAPPED lpOverlapped,
    __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );


第一个参数hFile:要操作的设备句柄

第二个参数lpBuffer:读入数据的缓冲区

第三个参数nNumberOfBytesToRead:需要读取的字节数

第四个参数lpOverlapped:一个OVERLAPPED指针

第五个参数lpComletionRoutine:完成例程

需要注意的是,这里提供的OVERLAPPED不需要提供事件句柄。ReadFileEx将读请求传递到驱动程序后立刻返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调历程(CALL BACK FUNCTION)。这类似一个软中断,也就是当读操作结束后,系统立刻回调ReadFileEx提供的回调历程。Windows将这种机制称为异步过程调用(APC AsynchronousProcedureCall)

 

然后,APC的回调函数被调用是有条件的。只有线程处于警惕状态(Alert)时,回调函数才有可能被调用。有多个API可以使系统进入警惕状态,如

SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectEx函数等。

这些Win32 API都会有一个BOOL型的参数bAlertable,当设置TRUE时,就进入警惕模式。

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

回调历程会报告本次操作的完成状况,比如是成功或是失败。同时会报告本次读取操作实际读取字节数等。下面是一般回调历程的声明:

 

VOID CALLBACK FileIOCompletionRoutine( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped );


第一个参数dwErrorCode:如果读取错误,会返回错误码;

第二个参数dwNumberOfBytesTransfered:返回实际读取操作的字节数;

第三个参数lpOverlapped:OVERLAP参数,指定读取的偏移量等信息;

下面代码演示如何在应用程序中使用ReadFileEx进行异步读操作:

 

#include <windows.h>
#include <stdio.h>
#define DEVICE_NAME	"test.dat"
#define BUFFER_SIZE	512//假设该文件大于或等于BUFFER_SIZEVOID 
CALLBACK MyFileIOCompletionRoutine(  DWORD dwErrorCode,                // 对于此次操作返回的状态  
DWORD dwNumberOfBytesTransfered,  // 告诉已经操作了多少字节,也就是在IRP里的Infomation  
LPOVERLAPPED lpOverlapped         // 这个数据结构)
{	
	printf("IO operation end!\n");
}
int main()
{	
	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);	
	return 0;
}
三、IRP的同步和异步完成

1.IRP的同步完成

下面介绍Win32 API函数是如何一层层通过调用进入到派遣函数的。

 

(1)在应用程序中调用CreateFile Win32 API函数,这个函数用于打开设备

(2)CreateFile Win32 API函调用ntdll.dll中的NtCreateFile函数

(3)ntdll.dll中的NtCreateFile函数进入内核模式,然后调用ntoskrnl.exe中的NtCreateFile函数

(4)内核模式中ntoskrnl.exe的NtCreateFile函数创建IRP_MJ_CREATE类型的IRP,然后调用相应驱动程序的派遣函数,并将IRP的指针传递给该派遣函数

(5)派遣函数调用IoCompleteRequest,将IRP结束

(6)操作系统按原路返回,一直退到CreateFile Win32 API函数。至此CreateFile函数返回

(7)如果需要读取设备,应用程序调用ReadFile Win32 API函数

(8)ReadFile Win32 API调用ntdll.dll中的NtReadFile函数

(9)NtReadFile函数进入内核模式,调用ntoskenl.exe中的NtReadFile函数

(10)ntoskrnl.exe中的NtReadFile函数创建IRP_MJ_READ类型的IRP,并将其传入相应的派遣函数中

对设备进行读取可以有三种方法,第一种方式是用ReadFile函数进行同步读取,第二种是通过ReadFile方式进行异步读取,第三种方法是用ReadFileEx函数进行异步读取。

如果是用ReadFile进行同步读取时

(1)ReadFile函数内部会创建一个事件,这个事件连同IRP一起被传到派遣函数中(这个事件是IRP的UserEvent子域

(2)派遣函数用IoCompleteRequest时,IoCompleteRequest内部会设置IRP的UserEvent事件

(3)操作系统按照原路一只返回到ReadFile函数,ReadFile等待这个事件,因为该事件已经被设置,所以无需等待

(4)如果在派遣函数中没有调用IoCompleteRequest函数,该事件就没有被设置,ReadFile会一直等IRP结束

如果使用ReadFile进行异步读取

(1)这时,ReadFile内部不会创建事件,但ReadFile函数会接受overlap参数,overlap参数中会提供一个事件,这个事件被用作同步处理

(2)IoCompleteRequest内不会设置overlap提供的事件

(3)在ReadFile函数退出前后,它不会检测该事件是否被设置,因此可以不等待操作是否被完成

(4)当IRP操作被结束后,overlap提供的事件被设置,这个事件会通知应用程序IRP请求被完成

如果使用ReadFileEx函数进行异步读取

(1)ReadFileEx不提供事件,但是提供一个回调函数,这个回调函数的地址会作为IRP的参数传递给驱动程序

(2)IoCompleteRequest会将这个函数插入APC队列

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

 

2.IRP的异步完成

IRP被“异步完成”指的是不在派遣函数中调用IoCompleteRequest内核函数。调用IoCompleteRequest意味着IRP请求的结束,也标志着本次对设备操作的结束。

IRP是被异步完成,而发起IRP的应用程序会有三种发起IRP的形式,分别是ReadFile同步读取,ReadFile异步读取,ReadFileEx异步读取。

 

(1)IRP是由ReadFile的同步操作引起的:当派遣函数退出时,由于IoCompleteRequest没有被调用,IRP请求没有被结束,ReadFile会一直等待,知道操作结束。

(2)IRP是由ReadFile的异步操作引起的:当派遣函数退出时,由于IoCompleteRequest没有被调用,IRP请求没有被结束,但是ReadFile会立刻返回,返回值为失败,但代表操作没有完成。通过调用GetLastError函数,可以得到这时的错误代码是ERROR_IO_PENDING。这不是真正的错误,而是意味着ReadFile并没有真正完成操作,ReadFile只是异步返回。当IRP请求被真正的结束,即调用了IoCompleteRequest,ReadFile提供的overlap的事件才会被设置。这个事件可以通知了应用程序ReadFile的请求真正被执行完毕。

(3)IRP是由ReadFileEx操作异步引起的:ReadFileEx会立刻返回,但是返回值是FALSE,说明操作没有成功。调用GetLastError会发现错误码是ERROR_IO_PENDING,表明当前操作被“挂起”。当IRP结束后,ReadFileEx提供的回调历程会被插入到APC队列中。一旦操作系统进入警惕状态,线程的APC队列会自动出列。

 

如果派遣函数不调用IoCompleteRequest函数,则需要告诉操作系统此IRP处于“挂起”状态。这需要调用内核函数IoMarkIrpPending。同时,派遣函数应该返回STATUS_PENDING。下面代码演示了派遣函数异步处理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_READ被挂起,这里使用一个队列,也就是把每个挂起的IRP_MJ_READ的指针都插入队列,最后IRP_MJ_CLEANUP的派遣函数将一个个IRP出队列,并且调用IoCompleteRequest函数将他们结束

首先要定义队列的数据结构,(关于驱动中链表的操作能参考文章<<Window XP驱动开发(二十)Window驱动的内存管理 >>)

typedef struct _MY_IRP_ENTRY
{	
	PIRP pIRP;	
	LIST_ENTRY ListEntry;
} MY_IRP_ENTRY, *PMY_IRP_ENTRY;

 

在关闭设备的时候,会产生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;	
	//将存在队列中的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, LIST_ENTRY);		
		my_irp_entry->pIRP->IoStatus.Status = STATUS_SUCCESS;		
		my_irp_entry->pIRP->IoStatus.Information = 0;		
		IoCompleteRequest(my_irp_entry->pIRP,IO_NO_INCREMENT);		
		ExFreePool(my_irp_entry);	
	}	
	//处理IRP_MJ_CLEANUP的IRP	
	NTSTATUS status = STATUS_SUCCESS;	
	//完成IRP	
	pIrp->IoStatus.Status = status;	
	pIrp->IoStatus.Information = 0;	
	IoCompleteRequest(pIrp,IO_NO_INCREMENT);	
	KdPrint(("Leave HelloDDKCleanUp\n"));	
	return STATUS_SUCCESS;
}


3.取消IRP

 

除了将“挂起”的IRP插入队列,并在关闭设备时,将“挂起”的IRP结束,还有另外一个办法可以将“挂起”的IRP逐个结束,这就是取消IRP请求。内核函数IoSetCancelRoutine可以设置取消IRP请求的回调函数,其声明如下:

PDRIVER_CANCEL    IoSetCancelRoutine(PIRP Irp,IN PDRIVER_CANCEL CancelRoutine	);

第一个参数Irp:这个参数是需要取消的IRP

第二个参数CancelRoutine:这个是取消函数的函数指针。一旦IRP取消的时候,操作系统会调用这个取消函数。

返回值:标志是否操作成功

下面代码演示如何编写取消例程:

VOIDCancelReadIRP(	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;	
	IoCompleteRequest(Irp,IO_NO_INCREMENT);	
	//释放Cancel自旋锁	
	IoReleaseCancelSpinLock(Irp->CancelIrql);	
	KdPrint(("Leave CancelReadIRP\n"));
}
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{	
	KdPrint(("Enter HelloDDKRead\n"));	
	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;	
	IoSetCancelRoutine(pIrp,CancelReadIRP);
	//将IRP设置为挂起	
	IoMarkIrpPending(pIrp);	
	KdPrint(("Leave HelloDDKRead\n"));	
	//返回pending状态	
	return STATUS_PENDING;
}

四、StrartIO历程

StartIO历程能够保证各个并行的IRP顺序执行,即串行化

 

1.并行执行与串行执行

在很多情况下,对设备的操作必须是串行执行而不是并行执行。因此,驱动程序有必要讲并行的请求变换成串行请求。这需要用到队列,如果想依次处理每个IRP,必须采用队列将处理串行化。采用原则是“先来先服务”。

当一个新的IRP请求到来时,首先检查设备是否处于“忙”状态。设备在初始化的时候设置为“空闲”状态。当设备处于“空闲”状态时,可以处理一个IRP得请求,并改变当前设备为“忙”状态。如果设备处于“忙”状态,则将新来的IRP插入队列,并立刻返回,IRP留在以后处理。

当设备由“忙”转向“空闲”状态时,则从队列取出一个IRP进行处理,并重新将状态变为“忙”。

DDK为程序员提供了一个内部队列,并将IRP用StartIO例程串行处理。

2.StartIO历程

操作系统为程序员提供了一个IRP队列来实现串行,这个队列用KDEVICE_QUEUE数据结构表示:

//
// Device Queue object and entry
//

typedef struct _KDEVICE_QUEUE {
    CSHORT Type;
    CSHORT Size;
    LIST_ENTRY DeviceListHead;
    KSPIN_LOCK Lock;
    BOOLEAN Busy;
} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *RESTRICTED_POINTER PRKDEVICE_QUEUE;



这个队列的列头保存在设备对象Device_Object->DeviceQueue子域中。插入和删除队列中的元素都是由操作系统负责的。在使用这个队列的时候,需要向系统提供一个StartI例程,并将这个函数的函数名传递给操作系统,代码如下:

#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
			IN PDRIVER_OBJECT pDriverObject,
			IN PUNICODE_STRING pRegistryPath	) 
{
	//设置StartIO例程
	pDriverObject->DriverStartIo = HelloDDKStartIO;
}



这个StartIO历程运行在DISPATCH_LEVEL级别,要在声明时加上#pragram LOCKEDCODE,因此这个例程是不会被线程所打断的。

#pragma LOCKEDCODE
VOID
  HelloDDKStartIO(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  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);
	}

	KEVENT event;
	KeInitializeEvent(&event,NotificationEvent,FALSE);

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

	//定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右时间
	KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout);

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;	// no bytes xfered
	IoCompleteRequest(Irp,IO_NO_INCREMENT);


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

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



派遣函数如果想把IRP串行化,只需要加入IoStartPacket函数,就可以将IRP插入队列。并且IoStartPacket函数还可以让程序员指定其取消例程。

IoSatrtPacket首先判断当前设备处于“忙”还是“空闲”状态。如果设备“空闲”,则提升当前IRQL到DISPATCH_LEVEL级别,并进入StartIO例程“串行”处理该IRP。如果设备“忙”,则将IRP插入后返回。

在StartIO例程结束前,应该调用IoStartNextPacket函数,其作用是从队列中抽取下一个IRP,并将这个IRP作为参数调用StartIO例程。

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

	PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
			pDevObj->DeviceExtension;

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

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

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

	//返回pending状态
	return STATUS_PENDING;
}

 

在派遣函数中调用IoStartPacket内核函数指定取消例程。下面代码演示了如何编写取消例程

VOID
OnCancelIRP(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
	KdPrint(("Enter CancelReadIRP\n"));

	if (Irp==DeviceObject->CurrentIrp)
	{
		//表明当前正在改由StartIo处理
		//但StartIo并没有获取cancel自旋锁之前
		//这时候需要
		KIRQL oldirql = Irp->CancelIrql;

		//释放Cancel自旋锁
		IoReleaseCancelSpinLock(Irp->CancelIrql);

		IoStartNextPacket(DeviceObject,TRUE);

		KeLowerIrql(oldirql);
	}else
	{
		//从设备队列中将该IRP抽取出来
		KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,&Irp->Tail.Overlay.DeviceQueueEntry);
		//释放Cancel自旋锁
		IoReleaseCancelSpinLock(Irp->CancelIrql);
	}

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

	KdPrint(("Leave CancelReadIRP\n"));
}

4.自定义的StartIO

系统定义的StartIO例程只能使用一个队列,这个队列会将所有的IRP进行处理化。例如,读,写操作都会混在一起进行串行处理。然而,在有些情况下,需要将读,写分别进行串行处理。这时候就需要自定义StartIO例程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IRP的同步问题 的相关文章

随机推荐

  • 探究Android SQLite3多线程

    http www cnblogs com xyczero p 4096297 html 最近做项目时在多线程读写数据库时抛出了异常 这自然是我对SQlite3有理解不到位的地方 所以事后仔细探究了一番 关于getWriteableDataB
  • visio增加连接点

    在画系统架构图的时候遇到一个问题 如果一个图形本来有的连接点不够 需要在任何的位置上增加连接点 看了很多网络介绍 但是总是增加不成功 继续发现接下来问题揭晓 2013版本visio举例 首先在开始中找到连接点 其次 按住ctrl键 在想要添
  • CBOW 与 skip-gram

    skip gram是利用中间词预测邻近词 cbow是利用上下文词预测中间词 一 CBOW 1 continues bag of words 其本质是通过context来预测word CBOW之所以叫连续词袋模型 是因为在每个窗口内它也不考虑
  • Sublime Text自定义配色方案

    先推荐介绍几款非常好用的自定义主题 https github com aziz tmTheme Editor http tmtheme editor herokuapp com 这个可以在线修改配色方案 也可以上传本地的方案修改 https
  • linux源码文件数量,Linux 下统计文件夹大小及文件数量

    查看文件夹大小 lib 目录大小 du sh lib lib 子目录大小 du sh lib 查看 lib 目录下普通文件大小 find lib type f xargs ls la awk F BEGIN sum 0 sum 5 END
  • prim算法解决最小生成树问题

    刚好这次又遇到了prim算法 就做了下整理 可以参考 数据结构与算法分析c 描述 这本书 个人而言 很经典 并把以前写的代码也整理了一下 做下分享 同时也加深下自己的理解 prim算法是解决最小生成树问题的一个很好的算法 此算法是是将点集合
  • iOS - 常用的宏定义

    1 处理NSLog事件 开发者模式打印 发布者模式不打印 ifdef DEBUG define NSLog FORMAT fprintf stderr s d t s n NSString stringWithUTF8String FILE
  • 第九章 Qt拖放

    拖放是Qt实现的应用程序内或者多个应用程序之间传递信息的一种直观的现代操作方式 有没有想到windows的剪贴板 数据的移动和复制功能都异曲同工嘞 一 使拖放生效 拖放包含两个动作 拖动 和 放下 Qt窗口部件可以作为拖动点 drag si
  • NestedScrollView + RecyclerView完美解决显示不全及滑动冲突

  • color属性 python_Python cv2.CV_LOAD_IMAGE_COLOR属性代码示例

    需要导入模块 import cv2 as 别名 或者 from cv2 import CV LOAD IMAGE COLOR as 别名 def load cv2 img grayscale None TODO load images if
  • Hive连接报错,显示用户没有权限 org.apache.hadoop.ipc.RemoteException:User: xxx is not allowed to impersonate root

    Hive连接报错 显示用户没有权限 org apache hadoop ipc RemoteException User xxx is not allowed to impersonate root org apache hadoop ip
  • docker学习使用文档

    docker学习参考文档 学习途径 安装 介绍 环境准备 开始安装 卸载依赖 删除资源 阿里云镜像加速 底层原理 docker怎么工作 docker为什么比虚拟机快 1 docker有着比虚拟机更少的抽象层 2 docker利用的是宿主机内
  • 安卓ui开发教程下载!被面试官问的Android问题难倒了,内容太过真实

    前言 这些题目是网友去百度 小米 乐视 美团 58 猎豹 360 新浪 搜狐等一线互联网公司面试被问到的题目 熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率 欢迎一线公司员工以及网友提交面试题库 欢迎留言 网上的都是按照公司划分的
  • Linux实现多进程服务端Socket通信

    目录 程序流程 程序实例 运行结果 本例主要是让服务器能够同时处理多个客户端的连接请求 程序流程 1 创建基本的套接字 并绑定地址信息 设置监听 2 循环accept来接收连接请求 每接收一个连接请求 就创建一个子进程 3 在子进程中进行客
  • 浅谈人工智能专业,作为普通学生对未来的看法

    一 个人简介 本人是一个普通大学的普通学生 和大多数人一样 经历过高考志愿填报 在身边人的建议下 自己对于人工智能专业那听起来高大上的名字以及对于未知的探索渴望 我最终填报了人工智能专业 二 给普通学生的一段话 我真的和许许多多人一样就是一
  • Intel C and C++ Compilers: Features and Supported Platforms

    Submitted by Jennifer J Intel on April 20 2015 Share Tweet Share Intel C Compiler Features Supported in Different Produc
  • c++ 可变参数的三种实现方式

    c 可变参数 方法一 C语言的 va list1 include
  • 【python零基础入门学习】python基础篇(基础结束篇)之数据结构类型-列表,元组,字典,集合(五)

    本站以分享各种运维经验和运维所需要的技能为主 python零基础入门 python零基础入门学习 python运维脚本 python运维脚本实践 shell shell学习 terraform 持续更新中 terraform Aws学习零基
  • oracle 碎片是什么意思,Oracle碎片整理全面解析

    Oracle 作为一种大型数据库 广泛应用于金融 邮电 电力 民航等数据吞吐量巨大 计算机网络广泛普及的重要部门 对于系统管理员来讲 如何保证网络稳定运行 如何提高数据库性能 使其更加安全高效 就显得尤为重要 作为影响数据库性能的一大因素
  • IRP的同步问题

    转载自 http zhan renren com debugman tagId 178558 page 2 checked true 一 前言 对设备的任何操作都会最终转化为IRP请求 而IRP一般都是由操作系统异步发送的 异步处理IRP有