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  文档。

您好!下面是一些简要的 VMware Workstation 15 安装教程步骤: 1. 首先,您需要前往 VMware 官方网站(https://www.vmware.com/products/workstation-pro.html)下载 VMware Workstation 15 的安装程序。 2. 下载完成后,运行安装程序。如果您使用的是 Windows 操作系统,双击下载的安装程序(后缀为 .exe),如果您使用的是 macOS,双击下载的安装程序(后缀为 .dmg)。 3. 在安装程序运行时,您可能会被要求提供管理员权限以继续安装过程。请确保您具有管理员权限,并按照提示操作。 4. 在安装程序启动后,您将会看到安装向导。请按照向导的指示进行操作。首先,点击 "Next"(下一步)按钮。 5. 在接下来的屏幕上,您将被要求接受许可协议。请阅读许可协议并同意后,再次点击 "Next"(下一步)按钮。 6. 在下一个屏幕上,您可以选择安装类型。默认情况下,建议选择 "Typical"(典型)安装类型。如果您想要自定义安装,请选择 "Custom"(自定义)安装类型。然后再次点击 "Next"(下一步)按钮。 7. 接下来,您可以选择安装位置和文件夹。默认情况下,建议保持默认设置,并点击 "Next"(下一步)按钮。 8. 现在,您将在下一个屏幕上看到一个选项,允许 VMware 收集分析数据。根据您的偏好进行选择,并点击 "Next"(下一步)按钮。 9. 在接下来的屏幕上,您可以选择是否创建 VMware Workstation 的快捷方式。根据需求进行选择,并点击 "Next"(下一步)按钮。 10. 最后,您将看到安装设置的摘要。请检查您的选择,并点击 "Install"(安装)按钮开始安装。 11. 安装过程可能需要一些时间,请耐心等待,直到安装完成。 12. 安装完成后,您可以选择启动 VMware Workstation,然后按照提示进行进一步配置和设置。 这些是安装 VMware Workstation 15 的基本步骤。希望对您有所帮助!如果您有任何其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值