本篇内容對應<竹林蹊徑>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]呼應!!!