大部分IRP都是由应用程序的Win32API函数发起的。这些Win32API本身就支持同步和异步操作。例如,ReadFile、WriteFile、和DeviceIoControl等,这些都有两种操作方式,一种是同步操作,另一种是异步操作。
1)同步操作设备
如果需要同步操作设备,在打开设备的时候就要指定以“同步”的方式打开设备。打开设备用CreateFile函数,其函数声明如下:
HANDLE CreateFile(
LPCTSTR lpFileName, //设备名
DWORD dwDesiredAccess, //访问权限
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全属性
DWORD dwCreationDisposition, //如何创建
DWORD dwFlagsAndAttributes, //设备属性
HANDLE hTemplateFile //文件模板
);
其中第六个参数dwFlagsAndAttributes是同步异步操作的关键。如果这个参数中没有设置FILE_FLAG_OVERLAPPED,则以后对该设备的操作都是同步操作,否则所有操作作为异步操作。
对设备操作的WIn32API,例如ReadFile, WriteFile和deviceIOControl函数,都会提供一个OVERLAP参数,如:
BOOL ReadFile(
HANDLE hFile, //设备句柄
LPVOID lpBuffer, //读取的缓冲区
DWORD nNumberOfBytesToRead, //读取的大小
LPDWORD lpNumberOfBytesRead, //实际读取的大小
LPOVERLAPPED lpOverlapped //overlapp参数
);
在同步操作设备时,其lpoverlapped参数设置为NULL。下面的代码演示了应用程序如何对设备进行同步读取。
int main()
{
//打开设备,这里以文件作为例子
HANDLE hDevice = CreateFile("test.dat",
GENERIC_READ|GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_HIDDEN,
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);
CloseHandle(hDevice);
return 0;
}
2)异步操作方式一
异步操作设备时主要需要设置OVERLAP参数,Windows中用一种数据结构OVERLAPPED表示。
typedef struct _OVERLAPPED{
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
}OVERLAPPED;
第三个参数Offset:操作设备会指定一个偏移量,从设备的偏移量进行读取。
第五个参数hEvent:这个事件用于该操作完成后通知应用程序。程序员可以初始化该事件为未激发,当操作设备结束后,即在驱动中调用IoCompleteRequest后,设置该设备为激发态。示例代码
int main()
{
HANDLE hDevice = CreateFile("test.dat",
GENERIC_READ|GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_HIDDEN|FILE_FLAG_OVERLAPPED,
NULL);
//判断是否打开成功
if(hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error/n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
DWORD dwRead;
OVERLAPPED overlap = {0};
overlap.hEvent = CreateEvent((NULL, FALSE, FALSE, NULL);
ReadFile(hDevice, buffer, BUFFER_SIZE,&dwRead, &overlap);
WaitForSingleObject(overlap.hEvent, INFINITE);
CloseHandle(hDevice);
return 0;
}
3)异步操作二
除了ReadFile和WriteFile函数外,还有两个API也可以实现异步读写,这就是ReadFileEx和WriteFileEx函数。
BOOL ReadFileEx(
HANDLE hFile, //设备句柄
LPVOID lpBuffer, //读取缓冲区
DWORD nNumberOfBytesToRead, //读取的字节数
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMMPLETION_ROUTINE lpCompletionRoutine //完成函数
);
第五个参数lpCompletioRoutine:完成例程。
需要注意的是这里提供的OVERLAPPED不需要提供事件句柄。ReadFileEx将读请求传递到驱动程序后立刻返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调例程。这里是一个软中断,也就是当读操作结束后,系统立刻回调例程。Windows将这种机制称为异步过程调用(APC)。
然而,APC的回调函数被调用是有条件的。只有线程处于警惕状态时,回调函数才有可能被调用。有多个API可以使系统进入警惕状态,如SleepEx,WaitForSingleObjectEx,WaitForMultipleObjectEx函数等。这些Win32 API都会有一个BOOL型的参数bAlertable,当设置为TRUE时,就进入警惕 模式。
当系统进入警惕模式后,操作系统会枚举当前线程的APC队列。驱动程序一旦结束读取操作,就会把ReadFileEx提供的完成例程插入到APC队列。回调例程的声明:
VOID CALLBACK FileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
LPOVERLAPPED lpOverlapped
);
示例代码:
VOID CALLBACK MyFileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
LPOVERLAPPED lpOverlapped
)
{
printf("IO operation end!/n");
}
int main()
{
HANDLE hDevice = CreateFile("test.dat",
GENERIC_READ|GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_HIDDEN|FILE_FLAG_OVERLAPPED,
NULL);
//判断是否打开成功
if(hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error/n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
OVERLAPPED overlap = {0};
ReadFileEx(hDevice, buffer, BUFFER_SIZE, &overlap, MyFileIOCompletionRoutine);
SleepEx(0, TRUE);
CloseHandle(hDevice);
return 0;
}