2016年10月4日 星期二

TDI Operations - XII

Closing a Transport Address or Control Channel

在關閉任何關聯的連接以後,一個TDI客戶準備好去關閉一個開啟的傳輸位址。

當一個客戶對於開啟的傳輸位址或是控制通道不在使用,它必須如下釋放這些物件:

以相同的方式,一個TDI客戶也可以關閉任何的控制通道。如果客戶端開啟一個表示為控制通道的檔案物件(藉由調用 IoGetDeviceObjectPointer 得到),則它必須將這個檔案物件作為參數,然後調用 ObDereferenceObject 來釋放這個物件。接下來,I/O管理員會向傳輸端的TdiDispatchCleanup以及TdiDispatchClose 例程提交IRPs

這些傳輸端的例程(routines)會立即的關閉傳輸位址或是控制通道,並且釋放所有有關客戶指定的傳輸驅動資源。例如,TdiDispatchCleanup取消所有在傳輸位址上等待處理(pending)請求,解除所有在此位址上註冊過的ClientEventXxx處理函式,並且清除此位上的客戶狀態。如果這個客戶已經釋放了傳輸位址上最後的file handle傳輸也會為此傳輸位址釋放內部的狀態。

在客戶端收到ZwClose的回傳以後,它就不能再向這個傳輸位址或是控制通道提交一個請求。這意味著這個表示為傳輸位址或是控制通道的檔案物件不復存在。

TDI Operations - XI

Closing a Connection Endpoint

下圖示範一個核心模式客戶如何關閉一個連接端點。









在一個點對點連線已經斷線以後,一個客戶可以關閉連接端點。當一個客戶不在需要使用一個開啟的連接端點(connection endpoint),它必須如下關閉連接端點:

接者,I/O管理員提交 IRPs 到傳輸端的 TdiDispatchCleanup,以及之後的 TdiDispatchClose 例程。

這些傳輸例程立即的關閉連接端點並且釋放所有相關的傳輸驅動資源。TdiDispatchCleanup 也透過發送一個斷線通知給對應的遠端節點傳輸終止所有的連線活動。


如前述, 在建立一個關閉連線端點請求之前,TDI客戶不需要撇清(disassociate)連接端點與它所關聯的傳輸位址。如有需要,底層傳輸會模擬一個撇清的影響。

然而,一個客戶可以在關閉連線端點之前,明確的透過 TDI_DISSOCIATE_ADDRESS 請求來撇清(disassociate)一個連接端點與一個開的傳輸位址,這可以使用 TdiBuildDisassociateAddress 來封裝這個請求。

TDI Operations - X

Disconnecting an Endpoint-to-Endpoint Connection

下圖示範一個TDI客戶如何釋放一個點對點連接。

斷線行為是傳輸特的性質。當一個連導向TDI客戶發起一個斷線,兩個節點會進行斷線操作。也就是說,當一個客戶發起一個斷線,則遠端節點客戶必須回應這個請求。

在一個斷線操作期間,TDI傳輸驅動在開啟的連接端點通常會拒絕進入的請求,並且在指定的連接端點停止所有的活動。

如上圖所示,一個客戶在一個點對點連線上可以透過提交一個 TDI_DISCONNECT 請求來發起一個斷線操作。客戶可以透過 TdiBuildDisconnect 來封裝並交給底層傳輸。當傳輸結束了客戶端的請求,它會通知遠端節點傳輸驅動一個斷線正在進行,並且這個傳輸在點對點連線上為了客戶提交的I/O請求開始回傳一個適合的狀態碼。

如果回應的客戶註冊它的 ClientEventDisconnect 處理函式,當斷線發生的時候TDI傳輸會通知客戶。接者,ClientEventDisconnect 建立一個 TDI_DISCONNECT 來對底層的傳輸確認這個斷線。這個通知允許回應的客戶立即的清除客戶為了點對點連線所配置的狀態。

然而,一個斷線操作並不會關閉客戶端所開的連接端點或是傳輸位址。在 TDI_DISCONNECT 完成以後,客戶還是可以重新使用這些檔案物件。例如,客戶在稍後建立網路連線,這在Making an Endpoint-to-Endpoint Connection有介紹過。一直到每個客戶關閉它的連接端點以及關聯的傳輸位址前,這些資源都會保留給客戶提交的IOCTL請求使用。這在稍後Closing a Connection Endpoint以及Closing a Transport Address or Control Channel分別的會被介紹。

TDI Operations - IX

Requesting Transport-Specific Actions

如果傳輸驅動有定義擴展TDI,則在下圖示範了一個TDI客戶如何建立一個傳輸指定動作 (transport-specific action) 請求。

一個TDI客戶端可以對TDI傳輸驅動發送一個特殊或專有的擴展請求(這些操作定義在一個傳輸指定動作碼的集合)。這些擴展可以是關注一個開的位址,一個開的連接端點,或是一個開的控制通道,這僅限定在調用的客戶端而不是其他任何的TDI傳輸客戶或是驅動。

要請求這樣一個傳輸定義動作操作,首先客戶必須開位址,連接端點,或是控制通道,就想之前提到的Opening a Transport Address, Opening a Connection Endpoint, 或是 Setting and Querying Information。接下來,客戶可以使用 TdiBuildAction 來封裝 TDI_ACTION 請求以及客戶提供的緩衝區(內含傳輸定義的動作碼(action code)以及相關的動作參數)


TDI Operations - VIII

Connection-Oriented Versus Connectionless Transfers

TDI提供一個在傳輸層上最小成本最高品質的連線導向服務。根據特定的驅動協定,這可以涉及客戶數據的打包,sequencing, acknowledgment, retransmission, flow-control, 以及 error recovery。 

TDI也提供數據報一個無連線服務。而這些服務都是輕量級的:它不需要提供error-free delivery 或是 flow control。傳輸階層通常不會分割或是重組數據報。在TDI傳輸上,傳遞無連線數據擁有最輕的負擔。

數據報的發送在本質上是一個不可靠的網路通信。發送的客戶無法得知在遠端節點位址上有多少個已經開的客戶,或是遠端節點客戶是否正在接收數據。此外,根據傳輸驅動作者的裁量權,本地節點TDI驅動可能會丟失或是複製一份數據報。相比之下,本地端節點傳輸在一個已經建立的點對點連線之下,在遠端節點傳輸接受數據並且回覆確認(acknowledged)之前,會負責重新嘗試發送數據。

就像一個數據報的發送,接收數據報的操作也是不可靠的。根據傳輸驅動作者的裁量權,本地節點TDI驅動可能會丟失或是複製一份接收到的數據報。相比之下,本地端節點傳輸在一個已經建立的點對點連線之下,在本地客戶接受(否決)數據並且回覆遠端節點確認(acknowledged)之前,會負責接收。

一個點對點連線會一直保持活動,直到一個斷線操作發生(在稍後的Disconnecting an Endpoint-to-Endpoint Connection會有介紹),或者是底層傳輸沒有收到遠端節點的回應的時候(超時以後斷線)

TDI Operations - VII

Sending and Receiving Connectionless Data

一旦核心模式的客戶端成功對底層傳輸開一個傳輸位址(transport address),他就可以在網路上使用無連線通信(connectionless communication)的方式傳輸數據。

要使用這種方式接收數據,客戶端必須向他的底層傳輸註冊它的ClientEventReceiveDatagram或者是明確的對底層傳輸發布一個TDI_RECEIVE_DATAGRAM請求。

更多有關開一個傳輸位址以及對指定的位址註冊一個事件處理函式,可以參考 Opening a Transport Address.

Sending a Datagram

下圖示範一個TDI客戶如何發送一個數據報(datagram)到遠端節點。


如本圖所示,發送一個數據報類似於發送一個連線導向的數據。然而,本地端節點客戶對於一個開的傳輸位址提交一個TDI_SEND_DATAGRAM請求,這取代了一個點對點連線上的發送方式。

一個TDI_SEND_DATAGRAM IRP請求底層傳輸發送一個客戶端提供的數據報到一個特定位址上的一個未知數量的遠端節點客戶。

本地端客戶可以使用TdiBuildSendDatagram來封裝這個請求。隨著目標的遠端節點位址,客戶端可以提供任何大小的緩衝區數據(在傳輸端允許範圍之內)。客戶端可以透過詢底層傳輸來確定這個長度限制,這在 Setting and Querying Information已經有介紹過了。

Receiving a Datagram

下圖示範了一個核心模式的客戶如何從它的底層傳輸接收一個來自於遠端節點的數據報(datagram)

如本圖所示,接收一個數據報類似於接收一個連線導向數據。然而,本地端節點客戶對於一個開啟的傳輸位址提交一個TDI_RECEIVE_DATAGRAM請求,這取代了一個點對點連線上的接收方式。

然而,客戶端可以接收來自於遠端節點所發送的數據報(此遠端節點位址是由本地客戶所開啟的傳輸位址作為數據報的目的地)。其它本地端客戶只要開啟相同的傳輸位址都可以接收相同的數據報。

一個TDI_RECEIVE_DATAGRAM IRP請求底層傳輸回傳一個來自於遠端節點的數據報。本地節點客戶可以使用TdiBuildReceiveDatagram來封裝這一個請求。隨著一個表示為開啟傳輸位址的檔案物件,客戶提供TDI傳輸驅動一個緩衝區(TDI傳輸所允許接收長度範圍之內)。由於TDI傳輸絕不會分段數據報,所以本地端節點客戶通常會發布一個接收數據報請求(receive-datagram request)來接收一個數據報。

當完成這樣一個請求時,傳輸驅動會複製一份接收的數據報到客戶所提供的緩衝區,並且會回傳遠端節點這個IRP已經完成。如果收到的數據報超出緩衝區大小,則傳輸會切斷超出的數據。

當完成這樣一個請求時,傳輸驅動會複製一份接收的數據報到客戶所提供的緩衝區,並且會回傳遠端節點這個IRP已經完成。如果收到的數據報超出緩衝區大小,則傳輸會切斷超出的數據。

客戶端也可以透過底層TDI傳輸驅動的事件通知來接收遠端的數據報。對於這些通知事件,傳輸驅動會移除TSDU的檔頭(來自遠端節點),並且調用客戶端所註冊的ClientEventReceiveDatagram或是ClientEventChainedReceiveDatagram處理函式。ClientEventReceiveDatagram可以執行下列操作之一:
  • 立即回傳一個未接受(not-accepted)的狀態,並且明確的告知傳輸驅動這個TSDU不是客戶端有興趣的。
  • 如果傳輸僅提供部分的數據,則複製部分數據到內部緩衝區並封裝一個TDI_RECEIVE_DATAGRAM來請求剩餘的TSDU數據,接著回傳控制權。
  • 複製所有的TSDU到內部緩衝區並回傳控制權。

如果客戶端沒有複製數據或是回傳一個IRP,則非緩衝(Nonbuffering)傳輸驅動可以在接收數據報事件指示後丟棄數據。緩衝式的傳輸驅動則會保留一定數量的數據報訊息,讓它的客戶可以在稍後透過明確的TDI_RECEIVE_DATAGRAM請求來重新獲得數據。

透過底層傳輸,ClientEventChainedReceiveDatagram處理函式總是獲得僅讀取(read-only access)完整TSDU的權限。隨後,這個例程不需要發布一個TDI_RECEIVE_DATAGRAM請求到底層傳輸。然而,客戶端負責立即的調用 TdiReturnChainedReceives 來回傳由NDIS微阜驅動所配置的資源。

2016年10月3日 星期一

TDI Operations - VI

Sending and Receiving Connection-Oriented Data

TDI客戶在不同的節點彼此傳送接收連線導向數據必須要建立一個點對點的連接。在傳輸數據之前,每一個客戶必須做以下的事情:
  1. 開啟一個傳輸位址( transport address)。
  2. 開啟一個連接端點(connection endpoint)。
  3. 關聯開啟的傳輸位址與連接端點。
  4. 建立彼此之間的點對點連接,一個負責發起連接請求,其他負責接收這個請求。
實際上,一旦本地節點傳輸對遠端節點傳輸發送一個連結接受偵(connection-acceptance frame),則接收連結請求的客戶就可以接受來自於遠端節點的數據。

在透過底層傳輸完成點對點連線以後,客戶端彼此之間可以跨過網路傳送數據。

Sending Data on an Endpoint-to-Endpoint Connection

下圖示範一個核心模式的客戶如何在一個點對點連接之間發送數據。

本地端節點的TDI客戶發布一個發送(send)請求,要求本地傳輸從連結端點發送一筆數據至遠端的連結端點。要做到這個,本地端節點的客戶會提交一個 TDI_SEND 的控制請求給他的底層傳輸。客戶端可以使用 TdiBuildSend 來包裝這個IRP,並且裡面包含了一個客戶端提供的緩衝區指標,其內容包含一個stream-oriented 或是message-oriented TSDU。這個緩衝區的最大長度只要在TDI傳輸允許的範圍之內就可以。如果傳輸支援快速發送(expedited sends),則客戶可以請求發布一個快速數據,這個請求會排在傳輸驅動佇列(queue)中其他已經在等待傳送的普通發送(normal sends)請求之前優先來處理。如果傳輸驅動支援內部緩衝,則客戶可以發布一個非阻塞(nonblocking)發送請求。

客戶調用 IoCallDriver 轉發TDI_SENDIRP到底層傳輸的TdiDispatchInternalDeviceControl例程。這個例程會檢MinorFunction  並更進一步地的調用內部的發送函式來處理這個IRP。發送請求,如果客戶已經提交其他的發送請求,並且這些請求還沒被處理,則內部驅動函式會將這個新的發送請求放置佇列當中。傳輸總是將快速請求數據排在正常的TSDUs之前處理。而無論是普通或是快速,都是使用FIFO的順序來發送客戶端要求的命令。在傳輸完成每一個客戶提交的TDI_SEND IRP之前,傳輸也會複製客戶提供的數據到內部緩衝區或是在網路中發送指定的數據。

如果底層傳輸的內部緩衝區不足,導致一個非阻塞(nonblocking)發送請求失敗。則當傳輸驅動有可用的緩衝區位置可以傳輸時,傳輸驅動會調用客戶註冊的 ClientEventSendPossible 處理函式, 通知客戶可以重新提交一個TDI_SEND的請求。

Receiving Data on an Endpoint-to-Endpoint Connection

下圖示範一個核心模式的客戶如何在點對點連接之間接收數據。


無論是普通(normal)或是快速(expedited),在兩端節點連線的情況下,本地端節點可以透過發送 TDI_RECEIVE 請求給底層傳輸來接收一個TSDU。客戶端可以使用 TdiBuildReceive 來封裝這個IRP,其內包含了客戶端提供的緩衝區位址(接收來自於傳輸端複製全部或是部分TSDU的數據)。緩衝區的長度可以是TDI傳輸驅動所允許的範圍之內。

客戶端調用IoCallDriver來轉發TDI_RECEIVE IRP給底層傳輸TdiDispatchInternalDeviceControl例程。此例程會檢MinorFunction 並且更進一步的調用內部函式來處理這個IRP。內部驅動會不間斷地轉移接收到的數據到客戶端所提供的緩衝區直到緩衝區收滿或是接收的TSDU枯竭。

然而,在接收操作期間,快速(expedited)數據的優先權高於普通(normal)數據。如果一個快速TSDU從一個遠端節點進入,而此時傳輸正在處理一個客戶提交的普通數據接收請求。則無論有多少普通的TSDU數據已經轉移到客戶端提供的緩衝區,傳輸端都會完成這個客戶端的IRP請求,接者,傳輸端會處理快速數據接收直到命令完成,而客戶端必須發布其他的TDI_RECEIVE來獲得剩餘的普通TSDU數據。


當一個底層TDI傳輸驅動通知一個事件發生,客戶也可以藉此接收一個來自於遠端節點的數據。對於這些通知事件,傳輸驅動會移除TSDU(從遠端節點所接收)的傳輸層檔頭(transport layer header), 並且調用客戶端所註冊的ClientEventReceive, ClientEventChainedReceive,ClientEventReceiveExpedited, ClientEventChainedReceiveExpedited 處理程序。

客戶端事件處理程序在稍後會複製數據。如果ClientEventReceive或是ClientEventReceiveExpedited沒有接收到所有的數據,他可以做以下的事情:
  • 立即返回未接受(not-accepted)的狀態,清楚的告知底層傳輸這個接收到的TSDU是客戶端不感興趣的。
  • 建立其他的TDI_RECEIVE接收請求來獲得剩餘的TSDU數據。
  • 依靠後續的驅動接收事件通知來獲得剩餘的數據。

底層傳輸驅動程式總是賦予ClientEventChainedReceiveClientEventChainedReceiveExpedited 處理程序僅讀取(read-only)完整TSDU的權限。因此,這些例程不需要發布一個 TDI_RECEIVE 的請求到底層傳輸,或是處理部分接收的TSDUs。然而,TDI客戶端驅動程式負責立即的調TdiReturnChainedReceives來返回相關聯的資源(初始由NDIS微阜驅動所配置)