網路驅動程式中的同步處理和通知
每當執行中的兩個執行緒共用可同時存取的資源時,不論是在單處理器電腦或對稱多處理器 (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,而另一個區段嘗試取得微調鎖定 B,同時按住微調鎖定 A,則會導致死結。 如果它取得一個以上的微調鎖定,驅動程式應該藉由強制執行擷取順序來避免死結。 也就是說,如果驅動程式在微調鎖定 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 為止。