27、Windows内核编程,IRP的同步(1)
时间:2010-11-08 来源:edwardlewiswe
对设备的任何操作都会转化为IRP请求,而IRP一般都是由操作系统异步发送的。但是有时需要同步来避免逻辑错误。同步方法有:StartIO例程,使用中断服务例程等。
1、应用程序对设备的同步异步操作
1)同步操作原理
大部分IRP是由应用程序的Win32 API发起。这些函数本身就支持同步异步操作。如ReadFile,WriteFile,DeviceIoControl等。
图示 IRP同步操作示意图P250
2)同步操作设备
如CreateFile,其参数dwFlagsAndAttributes 是同步异步的关键。没有设置FILE_FLAG_OVERLAPPED为同步,否则为异步。
再如ReadFile,其参数lpOverlapped 如果设为NULL,则为同步。
示例代码 P252
3)异步操作设备
法一:通过OVERLAPPED 结构体。
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
第三个参数:操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量用一个64位整形表示。Offset为该偏移量的低32位整形,OffsetHigh为高32位整形。
hEvent用于操作完成后通知应用程序。我们可以初始化其为未激发状态,当操作设备结束后,即在驱动中调用IoCompleteRequest后,设备该事件激发态。
OVERLAPPED 结构使用前要清0,并为其创建事件。
示例代码 P254
法二:ReadFileEx与WriteFileEx 专门被用来异步操作。
BOOL WriteFileEx(
HANDLE hFile, // handle to output file
LPCVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPOVERLAPPED lpOverlapped, // overlapped buffer
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine
);
lpOverlapped不需要提供事件句柄。
ReadFileEx 将读请求传递到驱动程序后立即返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调函数(Call Back Function)lpCompletionRoutine 。类似于一个软中断。Windows将这种机制称为异步过程调用APC(asynchronous procedure call )。只有线程于Alert状态时,回调函数才能被调用。多个函数使系统进行Alert状态:SleepEx,WaitForMultipleObjectsEx,WaitForSingleObjectEx,etc。
OS一旦结束读取操作,就会把相应的completion routine插入到APC队列中。当OS进入Alert状态后,会枚举当前线程的APC队列。
示例代码 P255
2、IRP的同步完成和异步完成
两种方法,一种是在派遣函数中直接结束IRP,称为同步方法。另外一种是在派遣函数中不结束IRP,而让派遣函数直接返回,IRP在以后某个时候再进行处理,也称为异步方法。
比如在ReadFile中同步时,派遣函数在调用IoCompleteRequest时,IoCompleteRequest内部会设置IRP的UserEvent事件。
如果在ReadFile异步时,ReadFile不创建事件,但是接收overlap参数,IoCompleteRequest内部会设置overlap提供的事件。
在ReadFileEx异步中,IoCompleteRequest将ReadFileEx提供的回调函数插入到APC队列中。
异步时,通过GetLastError()得到ERROR_IO_INCOMPLETE。如果派遣函数不调用IoCompleteRequest,此时IRP处于挂起状态。处理方法见示例代码中。
同时,应用程序关闭设置时产生IRP_MJ_CLEANUP类型的IRP。可以在IRP_MJ_CLEANUP的派遣函数中调用IoCompleteRequest来结束那些挂起的IRP请求。
示例代码 P260
取消IRP
PDRIVER_CANCEL
IoSetCancelRoutine(
IN PIRP Irp,
IN PDRIVER_CANCEL CancelRoutine
);
来设置取消IRP请求的回调函数。也可以用来删除取消例程,当CancelRoutine为空指针时,则删除原来设置的取消例程。
用IoCancelIrp函数指定取消IRP请求。在IoCancelIrp内部,通过cancel lock来进行同步。
IoReleaseCancelSpinLock
IoAcquireCancelSpinLock
可以用CancelIo API 来取消IRP请求。在CancelIo内部会枚举所有没有被完成的IRP,依次调用IoCancelIrp。如果应用程序没有调用CancelIo,在应用程序关闭时也会自动调用CancelIo。
注意:cancel lock是全局自旋锁,占用时间不宜太长。
示例代码 P264