每當兩個執行執行緒共用可同時存取的資源時,無論是在單處理器電腦或對稱多處理器 (SMP) 電腦上,都需要同步處理它們。 例如,在單處理器電腦上,如果一個驅動程式函式正在存取共用資源,並被另一個以較高 IRQL 執行的函式中斷,例如 ISR,則必須保護共用資源,以防止競爭條件讓資源處於不確定狀態。 在 SMP 電腦上,兩個執行緒可能會同時在不同的處理器上執行,並嘗試修改相同的資料。 這類存取必須同步處理。
NDIS 提供旋轉鎖,您可以使用它來同步在相同 IRQL 執行的執行緒之間的共用資源存取。 當共用資源的兩個執行緒在不同的 IRQL 執行時,NDIS 會提供一種機制,可暫時提高較低 IRQL 程式代碼的 IRQL,以便序列化共用資源的存取權。
當執行緒相依於執行緒外部事件的發生時,執行緒會依賴通知。 例如,驅動程式可能需要在某個時段過去時收到通知,以便檢查其裝置。 或者,網路介面卡 (NIC) 驅動程式可能必須執行定期作業,例如輪詢。 計時器提供了這樣的機制。
事件提供兩種機制,讓兩個執行執行緒可用來同步處理作業。 例如,迷你端口驅動程式可以透過寫入裝置來測試 NIC 的中斷。 驅動程式必須等候中斷通知,確認作業成功。 您可以使用事件來同步處理等待中斷完成的執行緒與處理中斷的執行處理緒之間的作業。
本主題中的下列小節說明這些 NDIS 機制。
自旋鎖
自旋鎖提供同步機制,以保護在單處理器或多處理器電腦中以 IRQL PASSIVE_LEVEL 執行之核心模式執行緒所共用的資源。 自旋鎖負責處理在對稱多處理架構 (SMP) 電腦上同時執行的各種執行緒之間的同步。 執行緒會在存取受保護的資源之前取得自旋鎖。 自旋锁會阻止任何執行緒使用資源,但持有自旋锁的執行緒除外。 在 SMP 電腦上,正在旋轉鎖上等待的執行緒會不斷嘗試取得旋轉鎖,直到持有鎖的執行緒釋放它為止。
自旋鎖的另一個特性是相關的 IRQL。 嘗試取得旋轉鎖時,暫時會將請求執行緒的 IRQL 提高至與該旋轉鎖關聯的 IRQL。 這可防止相同處理器上所有較低的 IRQL 執行緒先占執行執行緒。 在相同處理器上執行較高 IRQL 的執行緒可以優先搶佔正在執行的執行緒,但這些執行緒無法取得自旋鎖,因為它的 IRQL 較低。 因此,在執行緒取得自旋鎖之後,其他執行緒無法取得自旋鎖,直到釋放為止。 撰寫良好的網路驅動程式可將微調鎖的保留時間降到最低。
自旋鎖的典型用途是保護佇列。 例如,迷你埠驅動程式傳送函式 MiniportSendNetBufferLists 可能會將通訊協定驅動程式傳遞給它的封包排入佇列。 因為其他驅動程式函式也會使用此佇列,所以 MiniportSendNetBufferLists 必須使用微調鎖定來保護佇列,讓一次只有一個執行程可以操作連結或內容。 MiniportSendNetBufferLists 會取得旋轉鎖,將封包新增至佇列後再釋放旋轉鎖。 使用自旋鎖可確保持有鎖定的執行緒是唯一修改佇列連結的執行緒,同時將封包安全地新增至佇列。 當迷你埠驅動程式將封包從佇列中取出時,這類存取會受到相同的自旋鎖保護。 執行修改佇列頭或組成佇列的任何連結欄位的指令時,驅動程式必須使用自旋鎖來保護佇列。
司機必須注意不要過度保護隊列。 例如,驅動程式可以在將封包排入佇列之前,於封包的網路驅動程式保留欄位中執行一些作業(例如,填入包含長度的欄位)。 驅動程式可以在自旋鎖所保護的程式代碼區域之外執行此動作,但必須在將封包排入佇列之前執行此動作。 在封包進入佇列並且當前執行緒釋放自旋鎖之後,驅動程式必須假設其他執行緒可以立即將封包移出佇列。
避免旋轉鎖定問題
若要避免可能的死結,NDIS 驅動程式應先釋放所有 NDIS 旋轉鎖,再呼叫 NdisXxxSpinlock 函式以外的 NDIS 函式。 如果 NDIS 驅動程式不符合此需求,可能會發生死結,如下所示:
保留 NDIS 微調鎖定 A 的執行緒 1 會呼叫 NdisXxx 函式,該函式會嘗試呼叫 NdisAcquireSpinLock 函式來取得 NDIS 微調鎖定 B。
保留 NDIS 微調鎖定 B 的執行緒 2 會呼叫 NdisXxx 函式,以呼叫 NdisAcquireSpinLock 函式來嘗試取得 NDIS 微調鎖定 A。
執行緒 1 和執行緒 2 各自等待對方釋放其旋轉鎖,導致死結。
Microsoft Windows 作業系統不會限制網路驅動程式同時持有多個旋轉鎖。 不過,如果驅動程式的一個區段嘗試在保留微調鎖定 B 時取得微調鎖定 A,而另一個區段嘗試在保留微調鎖定 A 時取得微調鎖定 B,則會導致死結。 如果它獲取多個自旋鎖,驅動程式應該遵循獲取的順序,以避免死結。 也就是說,如果驅動程式在自旋鎖 B 之前強制取得自旋鎖 A,則不會發生上述情況。
取得自旋鎖會將 IRQL 提高至 DISPATCH_LEVEL,並將舊的 IRQL 儲存在自旋鎖中。 釋放微調鎖定會將 IRQL 設定為微調鎖定中儲存的值。 由於 NDIS 有時會在PASSIVE_LEVEL輸入驅動程式,因此下列程式代碼序列可能會發生問題:
NdisAcquireSpinLock(A);
NdisAcquireSpinLock(B);
NdisReleaseSpinLock(A);
NdisReleaseSpinLock(B);
驅動程式不應該存取此序列中的微調鎖定,原因如下:
在釋放微調鎖定 A 和釋放微調鎖定 B 之間,程式碼會以 PASSIVE_LEVEL 而不是 DISPATCH_LEVEL 執行,而且會受到不適當的中斷。
釋放旋轉鎖 B 之後,程式碼會在 DISPATCH_LEVEL 執行,這可能會在更後面的時間點導致呼叫端發生故障,並出現 IRQL_NOT_LESS_OR_EQUAL 停止錯誤。
使用自旋鎖會影響效能,而且一般而言,驅動程式不應該使用過多的自旋鎖。 有時,通常不同的功能(例如,傳送和接收功能)會有輕微重疊,此時可以使用兩個旋轉鎖。 使用多個自旋鎖可能是一個值得的取捨,以便讓這兩個函式能夠在不同的處理器上獨立運作。
定時器
計時器用於輪詢或超時操作。 驅動程式會建立計時器,並將函式與計時器產生關聯。 當計時器中指定的期間到期時,會呼叫相關聯的函數。 計時器可以是一次性的,也可以是週期性的。 一旦設定了定期計時器,它將繼續在每個週期到期時觸發,直到明確清除為止。 每次發射時都必須重置一次性計時器。
計時器是藉由呼叫 NdisAllocateTimerObject 來建立和初始化,並藉由呼叫 NdisSetTimerObject 來設定。 如果使用非週期性計時器,則必須呼叫 NdisSetTimerObject 來重設。 計時器會藉由呼叫 NdisCancelTimerObject 來清除。
事件
事件可用來同步處理兩個執行執行緒之間的作業。 事件是由驅動程式配置,並藉由呼叫 NdisInitializeEvent 來初始化。 以 IRQL = PASSIVE_LEVEL 執行的執行緒會呼叫 NdisWaitEvent ,讓自己進入等候狀態。 當驅動程式執行緒等候事件時,它會指定等候時間上限,以及要等候的事件。 呼叫 NdisSetEvent 導致事件發出訊號,或指定的等候時間間隔上限到期時,以先發生者為準,即會滿足執行緒的等候。
一般而言,事件是由呼叫 NdisSetEvent 的合作執行緒所設定。 建立事件時,事件狀態為未發出訊號,必須設定後才能對等待中的執行緒發出訊號。 事件會保持發出訊號,直到呼叫 NdisResetEvent 為止。