對裝置的任何操作都會轉化為IRP請求,而IRP一般都是由作業系統異步發送的。但是有時需要同步來避免邏輯錯誤。同步方法有:StartIO例程,使用中斷服務例程等。
1、應用程式對裝置的同步異步操作
1)同步操作原理
大部分IRP是由應用程式的Win32 API發起。這些函數本身就支援同步異步操作。如ReadFile,WriteFile,DeviceIoControl等。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInBnauYTMkdjMiZ2MkFGZxEWNhNmYkVWM3M2N3M2M5YjZ3UGMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.jpg)
圖示 IRP同步操作示意圖P250
2)同步操作裝置
如CreateFile,其參數dwFlagsAndAttributes 是同步異步的關鍵。沒有設定FILE_FLAG_OVERLAPPED為同步,否則為異步。
再如ReadFile,其參數lpOverlapped 如果設為NULL,則為同步。
代碼
1 #include <windows.h>
2 #include <stdio.h>
3
4 #define BUFFER_SIZE 512
5 int main()
6 {
7 HANDLE hDevice =
8 CreateFile("test.dat",
9 GENERIC_READ | GENERIC_WRITE,
10 0,
11 NULL,
12 OPEN_EXISTING,
13 FILE_ATTRIBUTE_NORMAL,//此處沒有設定FILE_FLAG_OVERLAPPED
14 NULL );
15
16 if (hDevice == INVALID_HANDLE_VALUE)
17 {
18 printf("Read Error\n");
19 return 1;
20 }
21
22 UCHAR buffer[BUFFER_SIZE];
23 DWORD dwRead;
24 ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,NULL);//這裡沒有設定OVERLAP參數
25
26 CloseHandle(hDevice);
27
28 return 0;
29 }
示例代碼 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,并為其建立事件。
5 //假設該檔案大于或等于BUFFER_SIZE
6
7 #define DEVICE_NAME "test.dat"
8 int main()
9 {
10 HANDLE hDevice =
11 CreateFile("test.dat",
12 GENERIC_READ | GENERIC_WRITE,
13 0,
14 NULL,
15 OPEN_EXISTING,
16 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此處設定FILE_FLAG_OVERLAPPED
17 NULL );
18
19 if (hDevice == INVALID_HANDLE_VALUE)
20 {
21 printf("Read Error\n");
22 return 1;
23 }
24
25 UCHAR buffer[BUFFER_SIZE];
26 DWORD dwRead;
28 //初始化overlap使其内部全部為零
29 OVERLAPPED overlap={0};
30
31 //建立overlap事件
32 overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
33
34 //這裡沒有設定OVERLAP參數,是以是異步操作
35 ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);
36
37 //做一些其他操作,這些操作會與讀裝置并行執行
38
39 //等待讀裝置結束
40 WaitForSingleObject(overlap.hEvent,INFINITE);
41
42 CloseHandle(hDevice);
43
44 return 0;
45 }
46
47
示例代碼 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隊列。
4 #define DEVICE_NAME "test.dat"
5 #define BUFFER_SIZE 512
6 //假設該檔案大于或等于BUFFER_SIZE
7
8 VOID CALLBACK MyFileIOCompletionRoutine(
9 DWORD dwErrorCode, // 對于此次操作傳回的狀态
10 DWORD dwNumberOfBytesTransfered, // 告訴已經操作了多少位元組,也就是在IRP裡的Infomation
11 LPOVERLAPPED lpOverlapped // 這個資料結構
12 )
13 {
14 printf("IO operation end!\n");
15 }
16
17 int main()
18 {
19 HANDLE hDevice =
20 CreateFile("test.dat",
21 GENERIC_READ | GENERIC_WRITE,
22 0,
23 NULL,
24 OPEN_EXISTING,
25 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此處設定FILE_FLAG_OVERLAPPED
26 NULL );
28 if (hDevice == INVALID_HANDLE_VALUE)
29 {
30 printf("Read Error\n");
31 return 1;
32 }
34 UCHAR buffer[BUFFER_SIZE];
35
36 //初始化overlap使其内部全部為零
37 //不用初始化事件!!
38 OVERLAPPED overlap={0};
39
40 //這裡沒有設定OVERLAP參數,是以是異步操作
41 ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);
42
43 //做一些其他操作,這些操作會與讀裝置并行執行
44
45 //進入alterable
46 SleepEx(0,TRUE);
48 CloseHandle(hDevice);
49
50 return 0;
51 }
52
53
示例代碼 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請求。
//.h
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
#include <NTDDK.h>
}
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
#define arraysize(p) (sizeof(p)/sizeof((p)[0]))
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //裝置名稱
UNICODE_STRING ustrSymLinkName; //符号連結名
PLIST_ENTRY pIRPLinkListHead;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
typedef struct _MY_IRP_ENTRY
PIRP pIRP;
LIST_ENTRY ListEntry;
} MY_IRP_ENTRY, *PMY_IRP_ENTRY;
// 函數聲明
NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutin(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
//.cpp
#include "Driver.h"
/************************************************************************
* 函數名稱:DriverEntry
* 功能描述:初始化驅動程式,定位和申請硬體資源,建立核心對象
* 參數清單:
pDriverObject:從I/O管理器中傳進來的驅動對象
pRegistryPath:驅動程式在系統資料庫的中的路徑
* 傳回 值:傳回初始化驅動狀态
*************************************************************************/
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
NTSTATUS status;
KdPrint(("Enter DriverEntry\n"));
//設定解除安裝函數
pDriverObject->DriverUnload = HelloDDKUnload;
//設定派遣函數
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKRead;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = HelloDDKCleanUp;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutin;
//建立驅動裝置對象
status = CreateDevice(pDriverObject);
KdPrint(("Leave DriverEntry\n"));
return status;
* 函數名稱:CreateDevice
* 功能描述:初始化裝置對象
* 傳回 值:傳回初始化狀态
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject)
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//建立裝置名稱
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
//建立裝置
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj );
if (!NT_SUCCESS(status))
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
pDevExt->pIRPLinkListHead = (PLIST_ENTRY)ExAllocatePool(PagedPool,sizeof(LIST_ENTRY));
InitializeListHead(pDevExt->pIRPLinkListHead);
//建立符号連結
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink( &symLinkName,&devName );
IoDeleteDevice( pDevObj );
return STATUS_SUCCESS;
* 函數名稱:HelloDDKUnload
* 功能描述:負責驅動程式的解除安裝操作
pDriverObject:驅動對象
* 傳回 值:傳回狀态
#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
PDEVICE_OBJECT pNextObj;
KdPrint(("Enter DriverUnload\n"));
pNextObj = pDriverObject->DeviceObject;
while (pNextObj != NULL)
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pNextObj->DeviceExtension;
//删除符号連結
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
ExFreePool(pDevExt->pIRPLinkListHead);
pNextObj = pNextObj->NextDevice;
IoDeleteDevice( pDevExt->pDevice );
* 函數名稱:HelloDDKDispatchRoutin
* 功能描述:對讀IRP進行處理
pDevObj:功能裝置對象
pIrp:從IO請求包
IN PIRP pIrp)
KdPrint(("Enter HelloDDKDispatchRoutin\n"));
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//建立一個字元串數組與IRP類型對應起來
static char* irpname[] =
"IRP_MJ_CREATE",
"IRP_MJ_CREATE_NAMED_PIPE",
"IRP_MJ_CLOSE",
"IRP_MJ_READ",
"IRP_MJ_WRITE",
"IRP_MJ_QUERY_INFORMATION",
"IRP_MJ_SET_INFORMATION",
"IRP_MJ_QUERY_EA",
"IRP_MJ_SET_EA",
"IRP_MJ_FLUSH_BUFFERS",
"IRP_MJ_QUERY_VOLUME_INFORMATION",
"IRP_MJ_SET_VOLUME_INFORMATION",
"IRP_MJ_DIRECTORY_CONTROL",
"IRP_MJ_FILE_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CONTROL",
"IRP_MJ_INTERNAL_DEVICE_CONTROL",
"IRP_MJ_SHUTDOWN",
"IRP_MJ_LOCK_CONTROL",
"IRP_MJ_CLEANUP",
"IRP_MJ_CREATE_MAILSLOT",
"IRP_MJ_QUERY_SECURITY",
"IRP_MJ_SET_SECURITY",
"IRP_MJ_POWER",
"IRP_MJ_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CHANGE",
"IRP_MJ_QUERY_QUOTA",
"IRP_MJ_SET_QUOTA",
"IRP_MJ_PNP",
};
UCHAR type = stack->MajorFunction;
if (type >= arraysize(irpname))
KdPrint((" - Unknown IRP, major type %X\n", type));
else
KdPrint(("\t%s\n", irpname[type]));
//對一般IRP的簡單操作,後面會介紹對IRP更複雜的操作
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutin\n"));
KdPrint(("Enter HelloDDKRead\n"));
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;
KdPrint(("Enter HelloDDKCleanUp\n"));
//(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
KdPrint(("Leave HelloDDKCleanUp\n"));
//應用程式
#include <windows.h>
#include <stdio.h>
int main()
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);
//迫使程式中止2秒
Sleep(2000);
//建立IRP_MJ_CLEANUP IRP
CloseHandle(hDevice);
return 0;
示例代碼 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是全局自旋鎖,占用時間不宜太長。
1 VOID
2 CancelReadIRP(
3 IN PDEVICE_OBJECT DeviceObject,
4 IN PIRP Irp
5 )
7 KdPrint(("Enter CancelReadIRP\n"));
8
9 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
10 DeviceObject->DeviceExtension;
11
12 //設定完成狀态為STATUS_CANCELLED
13 Irp->IoStatus.Status = STATUS_CANCELLED;
14 Irp->IoStatus.Information = 0; // bytes xfered
15 IoCompleteRequest( Irp, IO_NO_INCREMENT );
17 //釋放Cancel自旋鎖
18 IoReleaseCancelSpinLock(Irp->CancelIrql);
19
20 KdPrint(("Leave CancelReadIRP\n"));
21 }
22
23
24 NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
25 IN PIRP pIrp)
26 {
27 KdPrint(("Enter HelloDDKRead\n"));
28
29 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
30 pDevObj->DeviceExtension;
31
32 IoSetCancelRoutine(pIrp,CancelReadIRP);
34 //将IRP設定為挂起
35 IoMarkIrpPending(pIrp);
37 KdPrint(("Leave HelloDDKRead\n"));
39 //傳回pending狀态
40 return STATUS_PENDING;
41 }
4 int main()
5 {
6 HANDLE hDevice =
7 CreateFile("\\\\.\\HelloDDK",
8 GENERIC_READ | GENERIC_WRITE,
9 0,
10 NULL,
11 OPEN_EXISTING,
12 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此處設定FILE_FLAG_OVERLAPPED
13 NULL );
14
15 if (hDevice == INVALID_HANDLE_VALUE)
16 {
17 printf("Open Device failed!");
18 return 1;
19 }
20
21 OVERLAPPED overlap1={0};
22 OVERLAPPED overlap2={0};
24 UCHAR buffer[10];
25 ULONG ulRead;
26
27 BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1);
28 if (!bRead && GetLastError()==ERROR_IO_PENDING)
30 printf("The operation is pending\n");
31 }
32 bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);
33 if (!bRead && GetLastError()==ERROR_IO_PENDING)
34 {
35 printf("The operation is pending\n");
36 }
37
38 //迫使程式中止2秒
39 Sleep(2000);
40
41 //顯式的調用CancelIo,其實在關閉裝置時會自動運作CancelIo
42 CancelIo(hDevice);
44 //建立IRP_MJ_CLEANUP IRP
45 CloseHandle(hDevice);
47 return 0;
48 }
示例代碼 P264