天天看点

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

继续阅读