NDIS 6.80 中的同步 OID 要求介面
Windows 網路驅動程式會使用 OID 要求,將控制訊息傳送至 NDIS 系結堆疊。 通訊協定驅動程式,例如 TCPIP 或 vSwitch,依賴數十個 OID 來設定基礎 NIC 驅動程式的每個功能。 在Windows 10 1709 版之前,OID 要求會以兩種方式傳送:一般和直接。
本主題介紹第三種 OID 呼叫樣式:同步。 同步呼叫的目的是要低延遲、非封鎖、可調整且可靠。 同步 OID 要求介面可從 NDIS 6.80 開始提供,此介面包含在 Windows 10 1709 版和更新版本中。
與一般和直接 OID 要求的比較
使用同步 OID 要求時,呼叫的承載 (OID 本身) 與一般和直接 OID 要求完全相同。 唯一的差異在於呼叫本身。 因此,這三種 OID 類型都相同;只有不同的方式。
下表說明一般 OID、Direct OID 和同步 OID 之間的差異。
屬性 | 一般 OID | 直接 OID | 同步 OID |
---|---|---|---|
Payload | NDIS_OID_REQUEST | NDIS_OID_REQUEST | NDIS_OID_REQUEST |
OID 類型 | Stats、Query、Set、Method | Stats、Query、Set、Method | Stats、Query、Set、Method |
可以發出者 | 通訊協定、篩選 | 通訊協定、篩選 | 通訊協定、篩選 |
可以完成者 | 迷你埠、篩選 | 迷你埠、篩選 | 迷你埠、篩選 |
篩選準則可以修改 | 是 | 是 | 是 |
NDIS 配置記憶體 | 每個篩選 (OID 複製) | 每個篩選 (OID 複製) | 只有在異常大量的篩選準則 (呼叫內容) |
可畫筆 | 是 | 是 | 否 |
可以封鎖 | 是 | 否 | 否 |
IRQL | == 被動 | <= DISPATCH | <= DISPATCH |
由 NDIS 序列化 | 是 | 否 | 否 |
會叫用篩選 | Recursively | Recursively | 迭 代 |
篩選準則複製 OID | 是 | 是 | 否 |
篩選
如同其他兩種類型的 OID 呼叫,篩選驅動程式在同步呼叫中完全控制 OID 要求。 篩選驅動程式可以觀察、攔截、修改和發出同步 OID。 不過,為了有效率,同步 OID 的機制有些不同。
傳遞、攔截和原點
概念上,所有 OID 要求都會從較高的驅動程式發出,並由較低驅動程式完成。 在過程中,OID 要求可能會通過任意數目的篩選驅動程式。
在最常見的案例中,通訊協定驅動程式會發出 OID 要求,而所有篩選只會將 OID 要求向下傳遞,未經修改。 下圖說明此常見案例。
不過,允許任何篩選模組攔截 OID 要求並完成。 在此情況下,要求不會傳遞至較低的驅動程式,如下圖所示。
在某些情況下,篩選模組可能會決定產生自己的 OID 要求。 此要求會從篩選模組的層級開始,而且只會周遊較低的驅動程式,如下圖所示。
所有 OID 要求都有此基本流程:較高的驅動程式 (通訊協定或篩選驅動程式) 發出要求,而較低驅動程式 (迷你埠或篩選驅動程式) 完成。
一般和直接 OID 要求的運作方式
定期或直接 OID 要求會以遞迴方式分派。 下圖顯示函式呼叫序列。 請注意,序列本身與上一節圖表中所述的序列非常類似,但會排列以顯示要求的遞迴本質。
如果已安裝足夠的篩選準則,NDIS 會強制配置新的執行緒堆疊,以更深入地遞迴。
NDIS 會將 NDIS_OID_REQUEST 結構視為只對堆疊的單一躍點有效。 如果篩選驅動程式想要將要求向下傳遞至下一個較低的驅動程式 (這是大部分 OID) 的情況,篩選驅動程式 必須 插入數十行的重複使用程式碼來複製 OID 要求。 此重複使用有數個問題:
- 它會強制記憶體配置複製 OID。 達到記憶體集區的速度很慢,而且無法保證 OID 要求的向前進度。
- OID 結構設計必須隨著時間維持不變,因為所有篩選驅動程式都會硬式編碼將某個NDIS_OID_REQUEST的內容複寫到另一個結構的機制。
- 需要這麼多的重複使用會遮蔽篩選真正執行的動作。
同步 OID 要求的篩選模型
同步 OID 要求的篩選模型會利用呼叫的同步本質,以解決上一節所討論的問題。
問題和完成處理常式
不同于一般和直接 OID 要求,同步 OID 要求有兩個篩選勾點:問題處理常式和完整處理常式。 篩選驅動程式無法註冊、一個或兩個勾點。
系統會針對每個篩選驅動程式叫用問題呼叫,從堆疊頂端向下到堆疊底部。 任何篩選準則的 [問題] 呼叫都可以停止 OID 向下繼續,並以某些狀態碼完成 OID。 如果沒有篩選準則決定攔截 OID,OID 就會到達 NIC 驅動程式,這必須同步完成 OID。
完成 OID 之後,會針對每個篩選驅動程式叫用 Complete 呼叫,從 OID 完成的堆疊開始,到堆疊頂端為止。 Complete 呼叫可以檢查或修改 OID 要求,並檢查或修改 OID 的完成狀態碼。
下圖說明一般案例,其中通訊協定發出同步 OID 要求,而篩選準則不會攔截要求。
請注意,同步 OID 的呼叫模型是反復的。 這會讓堆疊使用量保持固定,而不需要擴充堆疊。
如果篩選驅動程式在其 [問題] 處理常式中攔截同步 OID,則 OID 不會提供給較低的篩選或 NIC 驅動程式。 不過,仍會叫用較高篩選的完整處理常式,如下圖所示:
篩選準則攔截篩選準則攔截
最小記憶體配置
一般和 Direct OID 要求需要篩選驅動程式才能複製NDIS_OID_REQUEST。 相反地,不允許複製同步 OID 要求。 此設計的優點是同步 OID 的延遲較低 – OID 要求不會重複複製,因為它會向下移動篩選堆疊,而且發生失敗的機會較少。
不過,這會產生新的問題。 如果無法複製 OID,篩選驅動程式會在哪裡儲存其個別要求狀態? 例如,假設篩選驅動程式會將一個 OID 轉譯為另一個。 在堆疊下移時,篩選準則必須儲存舊的 OID。 在備份堆疊時,篩選準則必須還原舊的 OID。
為了解決此問題,NDIS 會為每個篩選驅動程式配置指標大小位置,以取得每個執行中的同步 OID 要求。 NDIS 會將此位置保留在從篩選的 Issue 處理常式到其 Complete 處理常式的呼叫之間。 這可讓問題處理常式儲存完成處理常式稍後取用的狀態。 下列程式碼片段為範例。
NDIS_STATUS
MyFilterSynchronousOidRequest(
_In_ NDIS_HANDLE FilterModuleContext,
_Inout_ NDIS_OID_REQUEST *OidRequest,
_Outptr_result_maybenull_ PVOID *CallContext)
{
if ( . . . should intercept this OID . . . )
{
// preserve the original buffer in the CallContext
*CallContext = OidRequest->DATA.SET_INFORMATION.InformationBuffer;
// replace the buffer with a new one
OidRequest->DATA.SET_INFORMATION.InformationBuffer = . . . something . . .;
}
return NDIS_STATUS_SUCCESS;
}
VOID
MyFilterSynchronousOidRequestComplete(
_In_ NDIS_HANDLE FilterModuleContext,
_Inout_ NDIS_OID_REQUEST *OidRequest,
_Inout_ NDIS_STATUS *Status,
_In_ PVOID CallContext)
{
// if the context is not null, we must have replaced the buffer.
if (CallContext != null)
{
// Copy the data from the miniport back into the protocol’s original buffer.
RtlCopyMemory(CallContext, OidRequest->DATA.SET_INFORMATION.InformationBuffer,...);
// restore the original buffer into the OID request
OidRequest->DATA.SET_INFORMATION.InformationBuffer = CallContext;
}
}
NDIS 會為每個呼叫的每個篩選儲存一個 PVOID。 NDIS 啟發學習法會在堆疊上配置合理的位置數目,以便在常見案例中沒有集區配置。 這通常不超過七個篩選準則。 如果使用者設定路徑案例,NDIS 會回復到集區配置。
減少重複使用
請考慮在 範例重複使用上重複使用,以處理一般或直接 OID 要求。 該程式碼只是註冊 OID 處理常式的專案成本。 如果您想要發出自己的 OID,您必須新增另一十行的重複使用。 使用同步 OID 時,不需要處理非同步完成的額外複雜度。 因此,您可以剪下大部分的重複使用專案。
以下是同步 OID 的最小問題處理常式:
NDIS_STATUS
MyFilterSynchronousOidRequest(
NDIS_HANDLE FilterModuleContext,
NDIS_OID_REQUEST *OidRequest,
PVOID *CallContext)
{
return NDIS_STATUS_SUCCESS;
}
如果您想要攔截或修改特定的 OID,只要新增幾行程式碼即可。 最小 Complete 處理常式甚至更簡單:
VOID
MyFilterSynchronousOidRequestComplete(
NDIS_HANDLE FilterModuleContext,
NDIS_OID_REQUEST *OidRequest,
NDIS_STATUS *Status,
PVOID CallContext)
{
return;
}
同樣地,篩選驅動程式只能使用一行程式碼發出自己的新同步 OID 要求:
status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);
相反地,需要發出一般或 Direct OID 的篩選驅動程式必須設定非同步完成處理常式,並實作一些程式碼,以區別其本身的 OID 完成與剛複製的 OID 完成。 此重複使用的範例顯示在 發出一般 OID 要求的範例重複使用上。
互通性
雖然一般、直接和同步呼叫樣式全都使用相同的資料結構,但管線不會移至迷你埠中的相同處理常式。 此外,某些管線中無法使用某些 OID。 例如, OID_PNP_SET_POWER 需要仔細同步處理,而且通常會強制迷你埠進行封鎖呼叫。 這會使直接 OID 回呼處理變得困難,並防止其在同步 OID 回呼中使用。
因此,與直接 OID 要求一樣,同步 OID 呼叫只能與 OID 子集搭配使用。 在 Windows 10 1709 版中,同步 OID 路徑僅支援在接收端調整第 2 版 (RSSv2) 中使用的OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES OID。
實作同步 OID 要求
如需在驅動程式中實作同步 OID 要求介面的詳細資訊,請參閱下列主題: