27、Windows内核编程,IRP的同步(1)

转载自 http://www.cnblogs.com/mydomain/archive/2010/11/08/1872222.html

27、Windows内核编程,IRP的同步(1)

对设备的任何操作都会转化为IRP请求,而IRP一般都是由操作系统异步发送的。但是有时需要同步来避免逻辑错误。同步方法有:StartIO例程,使用中断服务例程等。

1、应用程序对设备的同步异步操作

1)同步操作原理

大部分IRP是由应用程序的Win32 API发起。这些函数本身就支持同步异步操作。如ReadFile,WriteFile,DeviceIoControl等。

27、Windows内核编程,IRP的同步(1)_第1张图片

图示 IRP同步操作示意图P250

2)同步操作设备

如CreateFile,其参数dwFlagsAndAttributes 是同步异步的关键。没有设置FILE_FLAG_OVERLAPPED为同步,否则为异步。

再如ReadFile,其参数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;
 }


复制代码
示例代码 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,并为其创建事件。

代码
    
    
    
    
#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("Read Error\n");
         return 1;
     }
 
     UCHAR buffer[BUFFER_SIZE];
     DWORD dwRead;
 
     //初始化overlap使其内部全部为零
     OVERLAPPED overlap={0};
 
     //创建overlap事件
     overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
 
     //这里没有设置OVERLAP参数,因此是异步操作
     ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);
 
     //做一些其他操作,这些操作会与读设备并行执行
 
     //等待读设备结束
     WaitForSingleObject(overlap.hEvent,INFINITE);
 
     CloseHandle(hDevice);
 
     return 0;
 }

复制代码
示例代码 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队列。

代码
#include <windows.h>
 #include <stdio.h>
 
 #define DEVICE_NAME    "test.dat"
 #define BUFFER_SIZE    512
 //假设该文件大于或等于BUFFER_SIZE
 
 VOID CALLBACK MyFileIOCompletionRoutine(
   DWORD dwErrorCode,                // 对于此次操作返回的状态
   DWORD dwNumberOfBytesTransfered,  // 告诉已经操作了多少字节,也就是在IRP里的Infomation
   LPOVERLAPPED lpOverlapped         // 这个数据结构
 )
 {
     printf("IO operation end!\n");
 }
 
 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("Read Error\n");
         return 1;
     }
 
     UCHAR buffer[BUFFER_SIZE];
 
     //初始化overlap使其内部全部为零
     //不用初始化事件!!
     OVERLAPPED overlap={0};
 
     //这里没有设置OVERLAP参数,因此是异步操作
     ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);
 
     //做一些其他操作,这些操作会与读设备并行执行
 
     //进入alterable
     SleepEx(0,TRUE);
 
     CloseHandle(hDevice);
 
     return 0;
 }


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>
#ifdef __cplusplus
}
#endif 

#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,
                                 IN PIRP pIrp);
NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
                                 IN PIRP pIrp);
//.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
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice (
        IN PDRIVER_OBJECT    pDriverObject) 
{
    NTSTATUS status;
    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,
, TRUE,
                        &pDevObj );
    if (!NT_SUCCESS(status))
        return 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 );
    if (!NT_SUCCESS(status)) 
    {
        IoDeleteDevice( pDevObj );
        return status;
    }
    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请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutin(IN PDEVICE_OBJECT pDevObj,
                                 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"));

    return status;
}

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
                                 IN PIRP pIrp) 
{
    KdPrint(("Enter HelloDDKRead\n"));

    PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
            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;
}

NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
                                 IN PIRP pIrp) 
{
    KdPrint(("Enter HelloDDKCleanUp\n"));

    PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
            pDevObj->DeviceExtension;
    
    //(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
    NTSTATUS status = STATUS_SUCCESS;
    // 完成IRP
    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = 0;    // bytes xfered
    IoCompleteRequest( pIrp, IO_NO_INCREMENT );

    KdPrint(("Leave HelloDDKCleanUp\n"));
    return STATUS_SUCCESS;
}
//应用程序
#include <windows.h>
#include <stdio.h>

int main()
{
    HANDLE hDevice = 
        CreateFile("\\\\.\\HelloDDK",
                    GENERIC_READ | GENERIC_WRITE,
,
                    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);
    if (!bRead && GetLastError()==ERROR_IO_PENDING)
    {
        printf("The operation is pending\n");
    }
    
    //迫使程序中止2秒
    Sleep(2000);

    //创建IRP_MJ_CLEANUP IRP
    CloseHandle(hDevice);

    return 0;
}


取消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是全局自旋锁,占用时间不宜太长。

代码
    
    
    
    
VOID CancelReadIRP(
     IN PDEVICE_OBJECT DeviceObject,
     IN PIRP Irp
     )
 {
     KdPrint(("Enter CancelReadIRP\n"));
 
     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
             DeviceObject->DeviceExtension;
     
     //设置完成状态为STATUS_CANCELLED
      Irp->IoStatus.Status = STATUS_CANCELLED;
      Irp->IoStatus.Information = 0;    // bytes xfered
      IoCompleteRequest( Irp, IO_NO_INCREMENT );
 
     //释放Cancel自旋锁
     IoReleaseCancelSpinLock(Irp->CancelIrql);
 
     KdPrint(("Leave CancelReadIRP\n"));
 }
 
 
 NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
                                  IN PIRP pIrp) 
 {
     KdPrint(("Enter HelloDDKRead\n"));
 
     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
             pDevObj->DeviceExtension;
 
     IoSetCancelRoutine(pIrp,CancelReadIRP);
 
     //将IRP设置为挂起
     IoMarkIrpPending(pIrp);
 
     KdPrint(("Leave HelloDDKRead\n"));
 
     //返回pending状态
     return STATUS_PENDING;
 }

复制代码
代码
    
    
    
    
#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);
     if (!bRead && GetLastError()==ERROR_IO_PENDING)
     {
         printf("The operation is pending\n");
     }
     
     //迫使程序中止2秒
     Sleep(2000);
 
     //显式的调用CancelIo,其实在关闭设备时会自动运行CancelIo
     CancelIo(hDevice);
 
     //创建IRP_MJ_CLEANUP IRP
     CloseHandle(hDevice);
 
     return 0;
 }

示例代码 P264

你可能感兴趣的:(27、Windows内核编程,IRP的同步(1))