鱼C论坛

 找回密码
 立即注册
查看: 5057|回复: 1

StartIO例程

[复制链接]
发表于 2015-4-2 21:27:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
       有时候,对设备的操作必须是串行化的而不能并行执行。如果是同步IRP的话,用同步对象就可以搞定了,比如在IRP处理函数的开始获取Mutex,结束前释放Mutex。如果是异步IRP的话,就复杂一些了,我们可以在驱动里面自己维护一个状态和一个列表,比如当状态会空闲的时候就可以处理IRP,并把状态置为“忙”,然后又有一个IRP过来的时候,假设状态是“忙”,那么就将IRP放入列表中,等一个IRP处理结束后,就去处理列表里面的IRP,然后依次处理所有IRP。这样完全可以搞定,但是得费时间维护列表和状态。幸运的是,DDK已经提供了一个内部队列,这样就大大简化了程序员的工作,我们只需要将IRP往这个内部队列丢就可以了,然后通过StartIO例程串行化处理这些IRP请求。

StartIO例程

DDK提供的内部队列用KDEVICE_QUEUE数据结构表示,如下(从wdm.h中copy的)

typedef struct _KDEVICE_QUEUE {
    CSHORT Type;
    CSHORT Size;
    LIST_ENTRY DeviceListHead;
    KSPIN_LOCK Lock;

#if defined(_AMD64_)

    union {
        BOOLEAN Busy;
        struct {
            LONG64 Reserved : 8;
            LONG64 Hint : 56;
        };
    };

#else

    BOOLEAN Busy;

#endif

} KDEVICE_QUEUE, *PKDEVICE_QUEUE, *PRKDEVICE_QUEUE;
这个队列的列头保存在设备对象的DeviceObject->DeviceQueue里面。插入和删除都是系统搞定,我们无需关心。使用这个队列需要设置StartIo例程,比如:

#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
                                                                IN PUNICODE_STRING pRegistryPath)
{
        KdPrint(("Enter DriverEntry\n"));

        pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
        pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
        pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
        pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
        pDriverObject->MajorFunction[IRP_MJ_READ] = 
        pDriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] =
        pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
        pDriverObject->DriverUnload = HelloWDMUnload;
        pDriverObject->DriverStartIo = HelloWDMStartIO;
        

        KdPrint(("Leave DriverEntry\n"));
        return STATUS_SUCCESS;
}
通过pDriverObject->DriverStartIo = HelloWDMStartIO;设置StartIo例程。给出StartIo例程的实现:

void HelloWDMStartIO(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
        KdPrint(("++++Enter HelloWDMStartIO, IRP address: 0x%x\n", Irp));

        KIRQL oldirql;

        //自旋锁会将当前运行级别提高到DISPATCH_LEVEL,驱动程序如果想调用IoSetCancelRoutine,那么就得先获取这个lock,具体参考
        //http://msdn.microsoft.com/en-us/library/windows/hardware/ff548196(v=vs.85).aspx
        IoAcquireCancelSpinLock(&oldirql);

        //fdo->CurrentIrp就是驱动当前正在处理的IRP。
        if (Irp != fdo->CurrentIrp || Irp->Cancel)
        {//如果Irp不是当前处理的IRP,或者这个Irp是想取消的,那么直接返回,啥也不做。
                IoReleaseCancelSpinLock(oldirql);
                KdPrint(("Do nothing\n"));
                return;
        }
        else
        {//正在处理该IRP
                KdPrint(("Forbit to use CancelRoutine\n"));
                IoSetCancelRoutine(Irp, NULL);//不允许调用取消例程

                IoReleaseCancelSpinLock(oldirql);
        }

        //可以根据需要处理IRP,这里只处理IOCTL_ENCODE
        Encoding(fdo, Irp);

        //在队列中读取下一个IRP,并且进行StartIO.
        IoStartNextPacket(fdo, TRUE);

        KdPrint(("++++Leave HelloWDMStartIO, IRP address: 0x%x\n", Irp));
}
StartIo里面需要处理请求,比如这里调用Encoding函数。当然可以根据需要在StartIo里面处理IRP请求。Encoding函数的实现:

#pragma LOCKEDCODE
NTSTATUS Encoding(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
        PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

        //得到输入缓冲区大小
        ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;

        //得到输出缓冲区大小
        ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;

        //获取输入缓冲区,IRP_MJ_DEVICE_CONTROL的输入都是通过buffered io的方式
        char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;

        char temp[100] = {0};
        RtlCopyMemory(temp, inBuf, cbin);

        KdPrint(("----Start to Encode string: %s\n", temp));

        //假如需要将数据放到一个公共资源中,然后再进行操作,比如这里是亦或编码,那么就需要考虑同步的问题。
        //不然在多线程调用的时候,公共资源的访问将会有不可预测的问题。
        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

        RtlCopyMemory(pdx->buffer, inBuf, cbin);

        //模拟延时3秒
        KdPrint(("Wait 3s\n"));
        KEVENT event;
        KeInitializeEvent(&event, NotificationEvent, FALSE);
        LARGE_INTEGER timeout;
        timeout.QuadPart = -3 * 1000 * 1000 * 10;//负数表示从现在开始计数,KeWaitForSingleObject的timeout是100ns为单位的。
        KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);//等待3秒

        for (ULONG i = 0; i < cbin; i++)//将输入缓冲区里面的每个字节和m亦或
        {
                pdx->buffer[i] = pdx->buffer[i] ^ 'm';
        }

        //获取输出缓冲区,这里使用了直接方式,见CTL_CODE的定义,使用了METHOD_IN_DIRECT。所以需要通过直接方式获取out buffer
        KdPrint(("user address: %x, this address should be same to user mode addess.\n", MmGetMdlVirtualAddress(Irp->MdlAddress)));
        //获取内核模式下的地址,这个地址一定> 0x7FFFFFFF,这个地址和上面的用户模式地址对应同一块物理内存
        char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

        ASSERT(cbout >= cbin);
        RtlCopyMemory(outBuf, pdx->buffer, cbin);


        //完成irp
        Irp->IoStatus.Status = STATUS_SUCCESS;
        Irp->IoStatus.Information = cbin;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        KdPrint(("----Encode thread finished, string: %s\n",temp));

        return Irp->IoStatus.Status;
}

StartIo例程的递归

使用StartIo例程会接触到2个函数:IoStartPacket和IoStartNextPacket。

IoStartPacket:如果驱动不是“忙”状态,那么会调用StartIo例程;如果是“忙”状态那么就放入队列,立刻返回。MSDN:http://msdn.microsoft.com/en-us/library/windows/hardware/ff550370(v=vs.85).aspx

If the driver is already busy processing a request for the target device object, then the packet is queued in the device queue. Otherwise, this routine calls the driver'sStartIo routine with the specified IRP.

IoStartNextPacket: 如果队列中有请求需要处理的话,IoStartNextPacket会取出一个请求,调用StartIo例程。如果没有请求了,那么这个函数就直接返回。

从StartIo例程的实现里面可以看到,StartIo例程的最后会调用IoStartNextPacket函数,那么当队列里面还有请求的话,StartIo将会被递归调用。为了搞清楚这个递归过程,我画了个流程图:


                               
登录/注册后可看大图

从上图可以看到StartIo是个递归调用,直到队列里面所有的请求都处理完才返回。比方说:有3个IRP请求,每个请求会花费3秒钟。驱动在1秒内收到了这3个请求(假设是A,B,C三个请求,先后顺序是A,B,C)。那么StartIo的调用过程就是:


                               
登录/注册后可看大图

A的IoStartPacket函数要等处理完A,B,C三个请求才返回。而B和C的IoStartPacket函数将把IRP放入队列,然后马上返回。看一下DeviceIoControl的派遣函数:

NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
        KdPrint(("Enter HelloWDMIOControl\n"));

        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
        PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);


        //得到IOCTRL码
        ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;

        NTSTATUS status;
        ULONG info = 0;
        switch (code)
        {
        case IOCTL_ENCODE:
                {
                         
                         status = STATUS_PENDING;
                         Irp->IoStatus.Status = status;
                         Irp->IoStatus.Information = info;
                         IoMarkIrpPending(Irp);//将IRP设置为挂起状态(异步IRP)

                         /*********************************************************************************************************
                          调用IoStartPacket,IoStartPacket会调用驱动的StartIo或者将IRP放入内部队列(假如驱动状态是“忙”).
                          当驱动不是“忙”状态的时候,IoStartPacket将会调用StartIo例程。StartIo例程其实是递归操作,那么假如StartIo例程还没有结束的时候,
                          而这个时候又有caller请求过来,那么StartIo例程将进入递归调用。也就是说需要等Device Queue里面所有的请求全部处理完,StartIo例程
                          才返回。假如有3个请求,第一个请求将直接被执行,第二个第三个请求在第一个请求执行期间到达,也就是说第二个第三个请求将会被放在queue
                          里面,当第一个请求执行完毕的时候,StartIo例程会调用IoStartNextPacket处理队列,也就是说StartIo例程会递归2次来处理#2,#3请求。
                          等队列里面所有请求处理完毕,StartIo例程才一个个返回。那么第一个请求的StartIo例程相当于需要3x时间,第二个请求对应的StartIo例程需要
                          2x时间,第三个请求的StartIo需要1x时间。
                        ***********************************************************************************************************/
                         KdPrint(("start to call IoStartPacket, IRP: 0x%x\n", Irp));
                         IoStartPacket(fdo, Irp, 0, HelloWDMOnCancelIRP);
                         KdPrint(("end call IoStartPacket, IRP: 0x%x\n", Irp));
                }
                break;
        default:
                status = STATUS_INVALID_VARIANT;
                Irp->IoStatus.Status = status;
                Irp->IoStatus.Information = info;
                IoCompleteRequest(Irp, IO_NO_INCREMENT);
                break;
        }

        KdPrint(("Leave HelloWDMIOControl\n"));
        return status;
}




想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2016-2-6 13:07:38 | 显示全部楼层
学习,感谢分享!!!!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-22 10:05

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表