天天看點

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]呼應!!!