天天看點

27、Windows核心程式設計,IRP的同步(1)

對裝置的任何操作都會轉化為IRP請求,而IRP一般都是由作業系統異步發送的。但是有時需要同步來避免邏輯錯誤。同步方法有:StartIO例程,使用中斷服務例程等。

1、應用程式對裝置的同步異步操作

1)同步操作原理

大部分IRP是由應用程式的Win32 API發起。這些函數本身就支援同步異步操作。如ReadFile,WriteFile,DeviceIoControl等。

27、Windows核心程式設計,IRP的同步(1)

圖示 IRP同步操作示意圖P250

2)同步操作裝置

如CreateFile,其參數dwFlagsAndAttributes 是同步異步的關鍵。沒有設定FILE_FLAG_OVERLAPPED為同步,否則為異步。

再如ReadFile,其參數lpOverlapped 如果設為NULL,則為同步。

27、Windows核心程式設計,IRP的同步(1)
27、Windows核心程式設計,IRP的同步(1)

代碼

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,并為其建立事件。

27、Windows核心程式設計,IRP的同步(1)
27、Windows核心程式設計,IRP的同步(1)

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隊列。

27、Windows核心程式設計,IRP的同步(1)
27、Windows核心程式設計,IRP的同步(1)

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請求。

27、Windows核心程式設計,IRP的同步(1)
27、Windows核心程式設計,IRP的同步(1)

//.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是全局自旋鎖,占用時間不宜太長。

27、Windows核心程式設計,IRP的同步(1)
27、Windows核心程式設計,IRP的同步(1)

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 }

27、Windows核心程式設計,IRP的同步(1)
27、Windows核心程式設計,IRP的同步(1)

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

繼續閱讀