windows 驱动开发基础(二)事件通知---关于irp处理,DPC,链表等

代码来源及参考:wdk 7,路径:C:\WinDDK\7600.16385.1\src\general\event

   这个例子演示了两种关于当硬件事件发生时,驱动如何通知应用程序的方法。一种是基于event的,一种是基于IRP。但是样例驱动程序实际上没有与任何硬件通信,所以就通过 timer DPC 来模拟硬件事件。应用程序告诉驱动是通过设置event 信号还是完成 pending IRP 来通知自己,并给出一个相对的超时时限。

两种方法的处理流程:

1,基于event的方法

   应用程序通过CreateEvent()创建一个事件对象,然后通过自定义的 ioctl IOCTL_REGISTER_EVENT 发个驱动程序。由于驱动是单一的(monolithic),顶层的(toplevel),导致它的IRP 处理例程运行在与应用程序进程相同的环境中(process context 一样)----这一点有点神奇啊!,所以event 的句柄在驱动中也是有效的。驱动访问 event 对象句柄并保存器对象指针以备后用,然后将 自定义的 timer DPC 入队列。当DPC 超时,驱动通过KeSetEvent(),在 DISPATCH_LEVEL 将event 设置为有信号并删除 event 对象的访问。使用这种方法必须保证驱动是单一的,顶层的,如果不是这样,就无法保证进程环境与应用程序一致。

 2,基于 pending IRP 方法

   应用程序发送同步的 ioctl (IOCTL_REGISTER_EVENT)请求。驱动将 IRP 的状态标志为 pending并入队一个timer DPC 。当timer 超时(模拟的硬件事件),驱动完成对IRP的处理再通知应用程序。 

   下面看一下各函数的生命周期:

   首先,驱动被加载后最先被调用的应该是DriverEntry()。在DriverEntry()中,先创建设备对象,接着设置IRP 处理例程,最后初始化设备扩展结构体里面的数据。完成这些准备工作之后,DriverEntry()也就不会再被调用。剩下的就交给IRP 处理函数了。

{

    

    PDEVICE_OBJECT      deviceObject;
    PDEVICE_EXTENSION   deviceExtension;
    UNICODE_STRING      ntDeviceName;
    UNICODE_STRING      symbolicLinkName;
    NTSTATUS            status;

    UNREFERENCED_PARAMETER(RegistryPath);

    DebugPrint(("==>DriverEntry\n"));

    //
    // Create the device object
    //
    RtlInitUnicodeString(&ntDeviceName, NTDEVICE_NAME_STRING);

    status = IoCreateDevice(DriverObject,               // DriverObject
                            sizeof(DEVICE_EXTENSION), // DeviceExtensionSize
                            &ntDeviceName,              // DeviceName
                            FILE_DEVICE_UNKNOWN,        // DeviceType
                            FILE_DEVICE_SECURE_OPEN,    // DeviceCharacteristics
                            FALSE,                      // Not Exclusive
                            &deviceObject               // DeviceObject
                           );

    if (!NT_SUCCESS(status)) {
        DebugPrint(("\tIoCreateDevice returned 0x%x\n", status));
        return(status);
    }

    //
    // Set up dispatch entry points for the driver.
    //
    DriverObject->MajorFunction[IRP_MJ_CREATE]          = EventCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]           = EventCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP]         = EventCleanup;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = EventDispatchIoControl;
    DriverObject->DriverUnload                          = EventUnload;

    //
    // Create a symbolic link for userapp to interact with the driver.
    //
    RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_NAME_STRING);
    status = IoCreateSymbolicLink(&symbolicLinkName, &ntDeviceName);

    if (!NT_SUCCESS(status)) {
        IoDeleteDevice(deviceObject);
        DebugPrint(("\tIoCreateSymbolicLink returned 0x%x\n", status));
        return(status);
    }

    //
    // Initialize the device extension.
    //
    deviceExtension = deviceObject->DeviceExtension;

    InitializeListHead(&deviceExtension->EventQueueHead);

    KeInitializeSpinLock(&deviceExtension->QueueLock);

    deviceExtension->Self = deviceObject;

    //
    // Establish user-buffer access method.
    //
    deviceObject->Flags |= DO_BUFFERED_IO;

    DebugPrint(("<==DriverEntry\n"));

    ASSERT(NT_SUCCESS(status));

    return status;
}

通过调试信息看一下这些IRP  处理函数的调用顺序。命令行运行 应用程序: event.exe 10 0 ,延迟时限为10秒,通知方式是 基于 pending IRP 的。

从上图可以看出,DriverEntry()退出后;首先,调用的 IRP_MJ_CREATE的处理函数(对应应用层的CreateFile())进行一些资源申请,为后面处理IRP_MJ_DEVICE_CONTROL(对应应用层的DeviceIoControl())通信中的数据;接着,应用程序就可以和驱动通信。当应用程序结束通信并停止服务,这个时候IRP_MJ_CLEANUP的处理函数被调用,来清理现场;接着,IRP_MJ_CLOSE的处理函数(对应应用层的CloseHandle())进一步释放资源。最后,调用驱动卸载函数,整个驱动就结束运行。简化为下图所示:

                                         ring3                                                                   ring0

                                         CreateFile()                     --->                              IRP_MJ_CREATE

                                         DeviceIoControl()            <--->                             IRP_MJ_DEVICE_CONTROL

                                         CloseHandle                    --->                               IRP_MJ_CLEANUP,IRP_MJ_CLOSE

下面分别看看这3个阶段的处理流程:

相关结构体:

typedef struct _DEVICE_EXTENSION {
    PDEVICE_OBJECT  Self;
    LIST_ENTRY      EventQueueHead; // where all the user notification requests are queued
    KSPIN_LOCK      QueueLock;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;


typedef struct _NOTIFY_RECORD{
    NOTIFY_TYPE     Type;
    LIST_ENTRY      ListEntry;
    union {
        PKEVENT     Event;
        PIRP        PendingIrp;
    } Message;
    KDPC            Dpc;
    KTIMER          Timer;
    PFILE_OBJECT    FileObject;
    PDEVICE_EXTENSION   DeviceExtension;
    BOOLEAN         CancelRoutineFreeMemory;
} NOTIFY_RECORD, *PNOTIFY_RECORD;

typedef struct _FILE_CONTEXT{
    //
    // Lock to rundown threads that are dispatching I/Os on a file handle 
    // while the cleanup for that handle is in progress.
    //
    IO_REMOVE_LOCK  FileRundownLock;
} FILE_CONTEXT, *PFILE_CONTEXT;

IRP_MJ_CREATE/IRP_MJ_CLOSE对应的处理函数EventCreateClose()主要片段,当应用程序调用CreateFile()打开设备,与驱动建立会话;case IRP_MJ_CREATE 部分被调用。初始化 FILE_CONTEXT 结构体,里面就一个项目FileRundownLock,用来管理当前会话中的多次通信。

        case IRP_MJ_CREATE:
            DebugPrint(("IRP_MJ_CREATE\n"));

            fileContext = ExAllocatePoolWithQuotaTag(NonPagedPool, 
                                              sizeof(FILE_CONTEXT),
                                              TAG);

            if (NULL == fileContext) {
                status =  STATUS_INSUFFICIENT_RESOURCES;
                break;
            }

            IoInitializeRemoveLock(&fileContext->FileRundownLock, TAG, 0, 0);

            //
            // Make sure nobody is using the FsContext scratch area.
            //
            ASSERT(irpStack->FileObject->FsContext == NULL);    

            //
            // Store the context in the FileObject's scratch area.
            //
            irpStack->FileObject->FsContext = (PVOID) fileContext;
            
            status = STATUS_SUCCESS;
            break;

基于  pending IRP 的处理流程,在函数RegisterIrpBasedNotification()中通过设置 timer DPC 来模拟硬件事件,将 irp 设为pending  状态;当timer  超时,对应的DPC 函数被调用完成irp并通知应用程序。这是正常的情况,当应用程序提前申请结束与驱动程序的会话,而这个时候在驱动中有的 irp 还处于 pending 状态,无法等到timer 超时再处理 irp;现在只能中断irp  的处理流程,将无法正常完成的irp 状态设为 STATUS_CANCELED,并完成请求。

{
    PDEVICE_EXTENSION   deviceExtension;
    PNOTIFY_RECORD notifyRecord;
    PIO_STACK_LOCATION irpStack;
    KIRQL   oldIrql;
    PREGISTER_EVENT registerEvent;

    DebugPrint(("\tRegisterIrpBasedNotification\n"));

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    deviceExtension = DeviceObject->DeviceExtension;
    registerEvent = (PREGISTER_EVENT)Irp->AssociatedIrp.SystemBuffer;//获取应用程序传送的数据

    //
    // Allocate a record and save all the event context.
    //申请一个 记录用的结构体

    notifyRecord = ExAllocatePoolWithQuotaTag(NonPagedPool, 
                                              sizeof(NOTIFY_RECORD),
                                              TAG);

    if (NULL == notifyRecord) {
        return  STATUS_INSUFFICIENT_RESOURCES;
    }

    InitializeListHead(&notifyRecord->ListEntry);
    //往结构体里面填充数据
    notifyRecord->FileObject = irpStack->FileObject;
    notifyRecord->DeviceExtension = deviceExtension;
    notifyRecord->Type = IRP_BASED;
    notifyRecord->Message.PendingIrp = Irp;

    //
    // Start the timer to run the CustomTimerDPC in DueTime seconds to
    // simulate an interrupt (which would queue a DPC).
    // The user's event object is signaled or the IRP is completed in the DPC to
    // notify the hardware event.
    //

    // ensure relative time for this sample

    if (registerEvent->DueTime.QuadPart > 0) {
        registerEvent->DueTime.QuadPart = -(registerEvent->DueTime.QuadPart);
    }

    KeInitializeDpc(&notifyRecord->Dpc, // Dpc
                    CustomTimerDPC,     // DeferredRoutine
                    notifyRecord        // DeferredContext
                   );

    KeInitializeTimer(&notifyRecord->Timer);

    //
    // We will set the cancel routine and TimerDpc within the
    // lock so that they don't modify the list before we are
    // completely done.
    //
    KeAcquireSpinLock(&deviceExtension->QueueLock, &oldIrql);

    //
    // Set the cancel routine. This is required if the app decides to
    // exit or cancel the event prematurely.
    //
    IoSetCancelRoutine (Irp, EventCancelRoutine);

    //
    // Before we queue the IRP, we must check to see if it's cancelled.
    //
    if (Irp->Cancel) {

        //
        // Clear the cancel-routine automically and check the return value.
        // We will complete the IRP here if we succeed in clearing it. If
        // we fail then we will let the cancel-routine complete it.
        //
        if (IoSetCancelRoutine (Irp, NULL) != NULL) {

            //
            // We are able to successfully clear the routine. Either the
            // the IRP is cancelled before we set the cancel-routine or
            // we won the race with I/O manager in clearing the routine.
            // Return STATUS_CANCELLED so that the caller can complete
            // the request.

            KeReleaseSpinLock(&deviceExtension->QueueLock, oldIrql);

            ExFreePoolWithTag(notifyRecord, TAG);

            return STATUS_CANCELLED;
        } else {
            //
            // The IRP got cancelled after we set the cancel-routine and the
            // I/O manager won the race in clearing it and called the cancel
            // routine. So queue the request so that cancel-routine can dequeue
            // and complete it. Note the cancel-routine cannot run until we
            // drop the queue lock.
            //
        }
    }

    IoMarkIrpPending(Irp);

    InsertTailList(&deviceExtension->EventQueueHead,
                   &notifyRecord->ListEntry);

    notifyRecord->CancelRoutineFreeMemory = FALSE;

    //
    // We will save the record pointer in the IRP so that we can get to
    // it directly in the CancelRoutine.
    //
    Irp->Tail.Overlay.DriverContext[3] =  notifyRecord;

    KeSetTimer(&notifyRecord->Timer,   // Timer
               registerEvent->DueTime,         // DueTime
               &notifyRecord->Dpc      // Dpc
              );

    KeReleaseSpinLock(&deviceExtension->QueueLock, oldIrql);

    //
    // We will return pending as we have marked the IRP pending.
    //
    return STATUS_PENDING;;

}

关于自旋锁(SpinLock)的使用

  在上面例子中,为了管理pending irp会使用到链表(使用链表保存当前会话的通知记录);由于会话中的每次通信相当于多线程,所以这里存在同步的问题。当一个线程准备对链表进行插入或删除操作时,必须保证对链表的独占(此时其他线程无法操作链表),这里就是通过自旋锁来实现这一点的。

自旋锁的使用,避免了对链表(某种资源或数据)进行操作时发送不必要的冲突,解决多线程之间同步问题。通俗的来讲就是,大家约定上厕所要关门。

 

                        应用程序1--->DeviceIoControl

                                                                        ------>

                                                                                ------->

                                                                                            ----->                   event.sys-------->LIST_ENTRY(只有一个链表来  保存  所有的pending IRP)

                                                                                   ------>

                                                                           ------ >

                        应用程序2--->DeviceIoControl

详情请参考 wdk  文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值