对设备的任何操作都会最终转化为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。下面代码演示了应用程序如何对设备进行同步读取:
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 512
int main()
{
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);
return 0;
}
3.异步操作设备(方式一)
异步操作设备时主要需要OVERLAP参数,Windows中用一种数据结构OVERLAPPED表示。
typedef struct _OVERLAPPED
{
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
}OVERLAPPED;
第三个参数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(&