串行化IO

驱动串行化IO的理解

一、对于IoStartParket的理解:

VOID NTAPI IoStartPacket(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp,
                        IN PULONG Key, IN PDRIVER_CANCEL CancelFunction)
{
    BOOLEAN Stat;
    KIRQL OldIrql, CancelIrql;

    /* Raise to dispatch level */
    KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
    /* Check if we should acquire the cancel lock */
    if (CancelFunction)
    {   //如果给定了一个“取消函数”
        /* Acquire and set it */
        IoAcquireCancelSpinLock(&CancelIrql);
        Irp->CancelRoutine = CancelFunction;
    }
    /* Check if we have a key */
    if (Key)
    {   //对于加了锁的文件片段
        /* Insert by key */
        Stat = KeInsertByKeyDeviceQueue(&DeviceObject->DeviceQueue,
                                     &Irp->Tail.Overlay.DeviceQueueEntry, *Key);
    }
    else
    {   //对于一般的文件或设备
        /* Insert without a key */
        Stat = KeInsertDeviceQueue(&DeviceObject->DeviceQueue,
                                 &Irp->Tail.Overlay.DeviceQueueEntry);
    }
    /* Check if this was a first insert */
    if (!Stat)
    {   //这是针对目标设备对象的第一个IRP,可以启动
        /* Set the IRP */
        DeviceObject->CurrentIrp = Irp;
        /* Check if this is a cancelable packet */
        if (CancelFunction)
        {
            /* Check if the caller requested no cancellation */
            if (IoGetDevObjExtension(DeviceObject)->StartIoFlags &
                                                       DOE_SIO_NO_CANCEL)
            {
                /* He did, so remove the cancel routine */
                Irp->CancelRoutine = NULL;
            }
            /* Release the cancel lock */
            IoReleaseCancelSpinLock(OldIrql);
        }
        /* Call the Start I/O function */  //调用该设备对象所属驱动的I/O启动函数
        DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
    }
    else
    {   //目标设备对象的I/O操作已被启动但尚未完成,这是后续到达的IRP
        /* The packet was inserted... check if we have a cancel function */
        if (CancelFunction)
        {   //如果给定了取消函数
            /* Check if the IRP got cancelled */
            if (Irp->Cancel)
            {   //而且要取消
                /* Set the cancel IRQL, clear the currnet cancel routine and call ours */
                Irp->CancelIrql = CancelIrql;
                Irp->CancelRoutine = NULL;
                CancelFunction(DeviceObject, Irp);  //调用给定的取消函数
            }
            else
            {   //不取消
                /* Otherwise, release the lock */
                IoReleaseCancelSpinLock(CancelIrql);
            }
        }
    }
    /* Return back to previous IRQL */
    KeLowerIrql(OldIrql);
}

BOOLEAN NTAPI KeInsertDeviceQueue(IN PKDEVICE_QUEUE DeviceQueue,
                               IN PKDEVICE_QUEUE_ENTRY DeviceQueueEntry)
{
    KLOCK_QUEUE_HANDLE DeviceLock;
    BOOLEAN Inserted;
    ASSERT_DEVICE_QUEUE(DeviceQueue);

    /* Lock the queue */
    KiAcquireDeviceQueueLock(DeviceQueue, &DeviceLock);
    /* Check if it''''''''s not busy */
    if (!DeviceQueue->Busy)
    {   //目标设备对象空闲,不需要挂入队列,可以直接执行启动函数
        /* Set it as busy */
        Inserted = FALSE;
        DeviceQueue->Busy = TRUE;    //但是现在目标设备对象已经不再空闲
    }
    else
    {   //目标设备对象已经执行过启动函数但尚未完成,需要挂入队列
        /* Insert it into the list */
        Inserted = TRUE;
        InsertTailList(&DeviceQueue->DeviceListHead,
                                       &DeviceQueueEntry->DeviceListEntry);
    }
    /* Set the Insert state into the entry */
    DeviceQueueEntry->Inserted = Inserted;
    /* Release the lock */
    KiReleaseDeviceQueueLock(&DeviceLock);
    /* Return the state */
    return Inserted;    //返回TRUE表示已排入队列,FALSE表示可以继续操作
}


VOID OnCancel(PDEVICE_OBJECT fdo, PIRP Irp)
{

    if (fdo->CurrentIrp == Irp)
    {
        KIRQL oldirql = Irp->CancelIrql;
        IoReleaseCancelSpinLock(DISPATCH_LEVEL);
        IoStartNextPacket(fdo, TRUE);
        KeLowerIrql(oldirql);
    }
    else
    {
        KeRemoveEntryDeviceQueue(&fdo->DeviceQueue, &Irp->Tail.Overlay.DeviceQueueEntry);
        IoReleaseCancelSpinLock(Irp->CancelIrql);
    }
    CompleteRequest(Irp, STATUS_CANCELLED, 0);
}


(一)先看下以下伪代码:

应用端:

AThread
{

ReadFile(...);

}
BThread
{

ReadFile(...);

}

Main()
{
CreateThread(AThread);
CreateThread(BThread);
IOCannel();
}

内核端:

OnRead
{

IOStartParket(...);

}

OnCancel()
{


}

(二)伪代码说明:

AThread调用ReadFile,然后调用内核的OnRead,OnRead中调用IOStartParket=》线程1
BThread调用ReadFile,然后调用内核的OnRead,OnRead中调用IOStartParket=》线程2
MainThread调用IOCannel,然后调用内核OnCancel=》线程3
总共那个三个线程。

(三)线程1的IoStartParket面临以下两种线程的资源竞争:

(1)其他的IOStartParket线程(如线程2)
解释:IN PDRIVER_CANCEL CancelFunction,指明了是否支持Iocancel,如果不支持,则不考虑线程3.
IoStartPacket可以简写为下面函数体。
VOID NTAPI IoStartPacket(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp,
                        IN PULONG Key, IN PDRIVER_CANCEL CancelFunction)
{
    BOOLEAN Stat;
    KIRQL OldIrql, CancelIrql;

    /* Raise to dispatch level */
    KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
 
    /* Check if we have a key */
    if (Key)
    {   //对于加了锁的文件片段
        /* Insert by key */
        Stat = KeInsertByKeyDeviceQueue(&DeviceObject->DeviceQueue,
                                     &Irp->Tail.Overlay.DeviceQueueEntry, *Key);
    }
    else
    {   //对于一般的文件或设备
        /* Insert without a key */
        Stat = KeInsertDeviceQueue(&DeviceObject->DeviceQueue,
                                 &Irp->Tail.Overlay.DeviceQueueEntry);
    }
    /* Check if this was a first insert */
    if (!Stat)
    {   //这是针对目标设备对象的第一个IRP,可以启动
        /* Set the IRP */
        DeviceObject->CurrentIrp = Irp; 
        /* Call the Start I/O function */  //调用该设备对象所属驱动的I/O启动函数
        DeviceObject->DriverObject->DriverStartIo(DeviceObject, Irp);
    }
 
    /* Return back to previous IRQL */
    KeLowerIrql(OldIrql);
}

KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);这一行提高Irql的优先级为DISPATCH_LEVEL,
它的作用是在单个CPU的环境下避免同时调用IoStartPacket,但其实对于多CPU的计算机来说,

它是无法避免同时调用IoStartPacke的,那怎么办呢?

其实在KeInsertDeviceQueue中加了自旋锁,此时同时调用IoStartPacket也不会有问题,
因为,如果有一个线程在调用KeInsertDeviceQueue,那另外一个线程肯定是在等待,当第一个线程
退出KeInsertDeviceQueue后,第二个线程进入KeInsertDeviceQueue,此时会把它的请求插入请求列表中。
因为第二个线程成功把请求插入列表,那么它就不会执行DriverStartIo例程。
只要第一个线程没有调用KeRemoveEntryDeviceQueue,以后再有其他线程的请求,都会放入请求列表中。

最后的结果是只有第一个线程会持续运行,因为第一个线程执行DriverStartIo例程快到结束时,
会调用IOStartNextPacket来执行下一请求,IOStartNextPacket中就调用了KeRemoveEntryDeviceQueue函数。


那有没可能IOStartNextPacket在调用KeRemoveEntryDeviceQueue后,来不及取出第二个请求,
此时又来了一个请求执行了DriverStartIo例程,以致和IOStartNextPacket执行的DriverStartIo冲突呢?

这不可能,因为IOStartNextPacke中表示设备忙的标志位不会被改变,除非此时下个请求已经不存在。
只要表示设备忙的标志位处于忙状态,那新来的请求只会被插到请求列表中。如果下个请求不存在,此时
IOStartNextPacke调用了KeRemoveEntryDeviceQueue函数把设备置为空闲状态,马上返回。
此时如果有新的请求进来,就可以直接调用DriverStartIo,不用插到请求列表中。

(2)IoCannelFunction(如线程3)

 


解释:系统后台维护了一个Cancel自旋锁。再调用Cancel例程之前,系统会先获取该自旋锁,
      在Cancel例程获取自旋锁以后,IoStartPacket只能在两个地方等待:
      一个是在KeInsertDeviceQueue之前,一个在DriverStartIo例程入口处。
      所以,如果要支持IoCancel,DriverStartIo一定要在入口处获取Cancel自旋锁,见下函数体:

 VOID DriverStartIo(PDEVICE_OBJECT fdo, PIRP Irp)
 {
 KIRQL oldirql;
 IoAcquireCancelSpinLock(&oldirql);
 if (Irp != fdo->CurrentIrp || Irp->Cancel)
 {
 IoReleaseCancelSpinLock(oldirql);
 return;
 }
 else
 {
 IoSetCancelRoutine(Irp, NULL);
 IoReleaseCancelSpinLock(oldirql);
 }
 ...(如IOStartNextPacket)
 }
     如果IoCancel完毕,IoStartPacket取消等待,继续运行,
     如果IoStartPacket在KeInsertDeviceQueue之前等待,那他会把新来的请求入队或者直接调用
     DriverStartIo,这要根据DriverStartIo=>IOStartNextPacket执行的结果,IOStartNextPacket
     中如果还有请求,则IoStartPacket请求入队;否则,IoStartPacket直接调用DriverStartIo。
     如果IoStartPacket在DriverStartIo例程入口处等待,则往下执行,如果发现当前的请求被取消,
     则释放掉自旋锁,直接执行下一个请求。

    

以上如果对IoStartPacket还有疑问,建议看Walter Oney的Windows Driver Model。

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值