天天看点

WDF框架系列:同步域,运行级别

本篇内容对应<竹林蹊径>3.4.7对象同步一节。作者对WDF同步域及运行级别的解释停留在纸面,然而,这两个概念却涉及到WDF设备\WDF队列的初始化,因此,不得不探究其背后的原理。

1.同步范围

书上P77提到可以指定2种同步范围:设备同步范围及队列同步范围。首先需要确定什么时候设置同步范围?分别以WdfSynchronizationScopeDevice和WdfSynchronizationScopeQueue为关键字,搜索整个ddk sample代码,在以下文件中找到相关设置:

设备同步范围:

//serial\serial\pnp.c
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE (&attributes,
                                            SERIAL_DEVICE_EXTENSION);

    attributes.EvtCleanupCallback = SerialEvtDeviceContextCleanup;
    attributes.SynchronizationScope = WdfSynchronizationScopeDevice;

    status = WdfDeviceCreate(&DeviceInit, &attributes, &device);      

队列同步范围:

//general\echo\kmdf\autosync\queue.c
 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&queueAttributes, QUEUE_CONTEXT);

    queueAttributes.SynchronizationScope = WdfSynchronizationScopeQueue;
    queueAttributes.EvtDestroyCallback = EchoEvtIoQueueContextDestroy;
    status = WdfIoQueueCreate(
                 Device,
                 &queueConfig,
                 &queueAttributes,
                 &queue
                 );      

从上面的代码片可知:在创建device或queue时可以设置同步范围。新的问题出现了:为什么都在创建WDF对象前设置?让我们顺着WdfDeviceCreate源码中层层推进

> WdfDeviceCreate()
 > FxDevice::_Create();
  >    pDevice->Initialize();
   >     FxDevice::Initialize();
    >        status = ConfigureConstraints(DeviceAttributes);      

最终框架在FxDeviceBase::ConfigureConstraints中对同步范围进行了设置(同时也设置了运行级别):

NTSTATUS
FxDeviceBase::ConfigureConstraints(
    __in_opt PWDF_OBJECT_ATTRIBUTES ObjectAttributes
    )
{
    NTSTATUS            status;
    WDF_EXECUTION_LEVEL driverLevel;
    WDF_SYNCHRONIZATION_SCOPE driverScope;

    if (ObjectAttributes != NULL) {
        m_ExecutionLevel = ObjectAttributes->ExecutionLevel;
        m_SynchronizationScope = ObjectAttributes->SynchronizationScope;
    }
    //取出父对象的同步范围和运行级别
    m_Driver->GetConstraints(&driverLevel, &driverScope);
    //如果Device继承父对象(Driver)的属性,则依葫芦画瓢
    if (m_ExecutionLevel == WdfExecutionLevelInheritFromParent) {
        m_ExecutionLevel = driverLevel;
    }

    if (m_SynchronizationScope == WdfSynchronizationScopeInheritFromParent) {
        m_SynchronizationScope = driverScope;
    }
    //如果是运行级别是PASSIVE_LEVEL,为驱动同步创建MUTEX(因为MUTEX对象会造成线程等待失去CPU)
    if (m_ExecutionLevel == WdfExecutionLevelPassive) {
        m_CallbackLockPtr = new(GetDriverGlobals()) 
                                FxCallbackMutexLock(GetDriverGlobals());
    }
    else { //如果运行级别是DISPATCH_LEVEL,为驱动同步创建spin_lock
        m_CallbackLockPtr  = new(GetDriverGlobals()) 
                                FxCallbackSpinLock(GetDriverGlobals());
    }

    if (NULL == m_CallbackLockPtr) {
        ...
    }
    //晦涩的代码
    m_CallbackLockPtr->Initialize(this);
    m_CallbackLockObjectPtr = this; //this对于这个函数而言,是指FxDevice

    status = STATUS_SUCCESS;
    
Done:
    return status;
}      

在ConfigureConstraints中,框架首先根据WDF_OBJECT_ATTRIBUTE相关域设置即将被创建的Device的同步范围/运行级别属性。驱动同步往往通过MUTEX/SPIN_LOCK来实现,因此,框架根据coder设置的不同运行级别,创建适合运行在不同LEVEL上的同步对象。

代码中有两行容易被忽视的代码:

m_CallbackLockPtr->Initialize(this);
    m_CallbackLockObjectPtr = this;      

为什么说它重要?后面第二节会用到[注1]。查看Initialize的定义,发现它是一个纯虚函数:

class FxCallbackLock : public FxGlobalsStump {
...
    virtual
    void
    Initialize(
        FxObject* ParentObject
        ) = 0;
...
};      

要找到运行时执行的Initialize函数需要费点功夫:

Step1.m_CallbackLockPtr是定义在class FxDeviceBase中基类指针:

FxCallbackLock* m_CallbackLockPtr;      

Step2.在FxDeviceBase::ConfigureConstraints中,基类指针指向了不同派生类对象:

if (m_ExecutionLevel == WdfExecutionLevelPassive) {
        m_CallbackLockPtr = new(GetDriverGlobals()) 
                                FxCallbackMutexLock(GetDriverGlobals());
    }
    else { //如果运行级别是DISPATCH_LEVEL,为驱动同步创建spin_lock
        m_CallbackLockPtr  = new(GetDriverGlobals()) 
                                FxCallbackSpinLock(GetDriverGlobals());
    }      

Step3.定位到派生类FxCallbackMutexLock/FxCallbackSpinLock,查看它们如何实现Initialize函数:

class FxCallbackMutexLock : public FxCallbackLock {
...
    virtual
    void
    Initialize(
        FxObject* ParentObject
        )
    {
        PFX_DRIVER_GLOBALS fxDriverGlobals;

        m_Verifier = NULL;
        fxDriverGlobals = GetDriverGlobals();

        if (fxDriverGlobals->FxVerifierLock) {

            //
            // VerifierLock CreateAndInitialize failure is not fatal,
            // we just won't track anything
            //
            (void) FxVerifierLock::CreateAndInitialize(&m_Verifier,
                                                       fxDriverGlobals,
                                                       ParentObject,
                                                       TRUE);
        }
    }
...
};      

fxDriverGlobals->FxVerifierLock好像只有当开启Driver Verifier时才会非空,所以可以简单的认为这段if分支不会执行。

2.同步范围的应用

讲到这,还是看不出同步范围对驱动运行的影响,看官莫急,马上见分晓。我们来看看Queue对象的创建。它的调用流程和WdfDevice类似,也经历这样的过程:

FxIoQueue::_Create();
    FxIoQueue->Initialize();
      FxIoQueue::Initialize();
        status = ConfigureConstraints(DeviceAttributes);      

只是在FxIoQueue::ConfigureConstraints中略有不同:

if (m_SynchronizationScope == WdfSynchronizationScopeDevice) {
        NTSTATUS status;
...
        AutomaticLockingRequired = TRUE;
        m_CallbackLockPtr = m_Device->GetCallbackLockPtr(&m_CallbackLockObjectPtr); //<-------重要代码,和[注1]呼应!!!
    }
    else if (m_SynchronizationScope == WdfSynchronizationScopeQueue) {
        AutomaticLockingRequired = TRUE;
    }

    if (AutomaticLockingRequired) {
        //
        // If automatic locking has been configured, set the lock
        // on the FxCallback object delegates
        //
        m_IoDefault.SetCallbackLockPtr(m_CallbackLockPtr);
        m_IoStop.SetCallbackLockPtr(m_CallbackLockPtr);
        m_IoResume.SetCallbackLockPtr(m_CallbackLockPtr);
...
        
        m_IoCancelCallbackLockPtr = m_CallbackLockPtr;
    }
    else {
        //
        // No automatic locking specified
        //
        m_IoDefault.SetCallbackLockPtr(NULL);
        m_IoStop.SetCallbackLockPtr(NULL);
...
        
        m_IoCancelCallbackLockPtr = NULL;
    }      

FxIoQueue从父对象中获得同步范围和运行级别,FxIoQueue的父对象是谁?是前面说过FxDevice。还记得在前面[注1]中写到过两行容易被忽视的代码?第一行代码已经解释过,来看下第二行:

m_CallbackLockPtr->Initialize(this);
    m_CallbackLockObjectPtr = this;      

m_CallbackLockObjectPtr = this中this指针指向谁?应该是调用者FxDevice,即m_CallbackLockObjectPtr指向了FxDevice。

对于框架这样的设置,如果不把眼光放到其他函数中,可能会不知所以然;但是,如果把眼光投到FxIoQueue::ConfigureConstraints中,可能会理解框架的用意。在此之前看看GetCallbackLockPtr实现:

FxCallbackLock*
FxDeviceBase::GetCallbackLockPtr(
    __out_opt FxObject** LockObject
    )
{
    if (LockObject != NULL) {
        *LockObject = m_CallbackLockObjectPtr;
    }

    return m_CallbackLockPtr;
}      

FxIoQueue::ConfigureConstraints中执行这句后

m_CallbackLockPtr = m_Device->GetCallbackLockPtr(&m_CallbackLockObjectPtr); //<-------重要代码,和[注1]呼应!!!