2016年7月26日 星期二

SERIAL_TIMEOUTS structure

SERIAL_TIMEOUTS structure


SERIAL_TIMEOUTS可以指定串列阜(serial port)上讀寫操作的超時(time-out)參數。

ReadIntervalTimeout
一個讀取操作,允許兩個連續字元(bytes)間讀取的最大時間量,以毫秒(milliseconds)為單位。超出最大時間量表示讀取操作超時。這個最大時間量不適用於該第一字節的讀取之前的時間間隔(意思為第一字元讀取的時候才開始計時)。這個為零表示不使用間隔逾時。欲了解更多信息,請參見備註。

ReadTotalTimeoutMultiplier
一個讀取操作,允許每一個字元(bytes)讀取的最大時間量,以毫秒(milliseconds)為單位。超出最大時間量表示一個讀取操作時。欲了解更多信息,請參見備註。

ReadTotalTimeoutConstant
一個讀取操作,允許每一個讀取操作增加額外時間最大量,以毫秒(milliseconds)為單位。超出最大時間量表示一個讀取操作時。欲了解更多信息,請參見備註。

WriteTotalTimeoutMultiplier
一個寫入操作,允許每一個字元(bytes)寫入的最大時間量,以毫秒(milliseconds)為單位。超出最大時間量表示一個寫入操作時。欲了解更多信息,請參見備註。

WriteTotalTimeoutConstant
一個寫入操作,允許每一個寫入操作增加額外的時間最大量,以毫秒(milliseconds)為單位。超出最大時間量表示一個寫入操作時。欲了解更多信息,請參見備註。


Remarks
IOCTL_SERIAL_SET_TIMEOUTSIOCTL_SERIAL_GET_TIMEOUTS上面這兩個I/O控制請求會使用到 SERIAL_TIMEOUTS這個結構
IOCTL_SERIAL_SET_TIMEOUTS控制請求使用此結構來設定串列阜的讀寫逾時參數。
IOCTL_SERIAL_GET_TIMEOUTS I/O控制請求則使用此結構來重新取得先前透過IOCTL_SERIAL_SET_TIMEOUTS所設定的逾時參數。

當指定的bytes數量傳輸完或是讀寫請求逾時,在驅動實作上都表示讀寫請求成功完成。當指定的bytes數量傳輸完成,則回覆請求的狀態碼(status code)STATUS_SUCCESS,如果讀操作逾時則回覆系統核心STATUS_TIMEOUT

如果一個IRP_MJ_READ請求指定一個讀取操作要讀取Nₗ位元的長度,則串列阜所能允許讀取操作完成的最大的時間量Tₐₓ,其時間量計算如下:
Tₐₓ =N ReadTotalTimeoutMultiplier ReadTotalTimeoutConstant

當逾時發生,表示一個讀取請求的完成超出了最大時間量,並且驅動會回覆此請求STATUS_TIMEOUT。在I/O status block中的Information欄位會填寫逾時發生前所成功讀取到的位元長度。

如果一個 IRP_MJ_WRITE請求指定一個寫入操作要寫入Nₗ位元的長度,則串列阜所能允許寫入操作完成的最大的時間量Tₐₓ,其時間量計算如下:
Tₐₓ =Nₗ WriteTotalTimeoutMultiplier WriteTotalTimeoutConstant

當逾時發生,表示一個寫入請求的完成超出了最大時間量,並且驅動會回覆此請求STATUS_TIMEOUT。在I/O status block中的Information欄位會填寫逾時發生前已經成功寫入的位元長度。


允許讀寫完成的最大時間, Tₐₓ, 其計時總是起於串列阜開始操作此請求,而不是當客戶端遞交此請求時就開始計時。(LW: 客戶端的讀寫命令可能會在驅動中的佇列排隊,一直等到驅動要操作此讀寫請求的時候,才開始執行此讀寫請求的計時)

ReadIntervalTimeout, ReadTotalTimeoutMultiplier, ReadTotalTimeoutConstant 若皆為零,則讀取操作絕不會逾時。
WriteTotalTimeoutMultiplier , WriteTotalTimeoutConstant 皆為零,則寫入操作絕不會逾時。

如果ReadIntervalTimeout 為零,則在讀取操作中連續位元之間不存在最大的時間間隔,逾時時間間隔僅僅基於ReadTotalTimeoutMultiplier ,ReadTotalTimeoutConstant 

ReadTotalTimeoutMultiplier ,ReadTotalTimeoutConstant 皆為零,且ReadIntervalTimeout 小於MAXULONG並且大於零,當連續接收位元發生一對位元之間讀取時間超出了ReadIntervalTimeout則表示一個讀取操作逾時。如果這三個逾時參數都有使用,且串列阜內部緩衝區為空,則當讀取請求發送至此阜時,在此阜接收最少一個byte的新數據前,此請求都不會逾時。

如果ReadIntervalTimeout 參數設定的MAXULONG,而且另外兩個參數 ReadTotalTimeoutConstant 以及 ReadTotalTimeoutMultiplier 皆為零,則讀取請求會立即完成,即便沒有接收到新的數據。讀取請求也會回覆STATUS_SUCCESS

ReadIntervalTimeout , ReadTotalTimeoutMultiplier 若皆設定為MAXULONG,且ReadTotalTimeoutConstant 設定大於零且小於MAXULONG,則讀取請求的行為如下:

  • 如果串列阜內的輸入緩衝區有任何數據,則讀取請求立即完成輸入緩衝區內的位元數並且返回STATUS_SUCCESS
  • 如果輸入緩衝區內沒有任何的數據,則串列阜會一直等待。直到一個byte到達,並在同時立即完成一個byte的讀取完成以及回傳STATUS_SUCCESS的狀態碼。
  • 如果在指定的ReadTotalTimeoutConstant時限內都沒有數據到達,則讀取請求逾時。設定I/O status block 中的Information 欄位為零,並且回傳STATUS_TIMEOUT的狀態碼。

若客戶端提交一個IOCTL_SERIAL_SET_TIMEOUTS 並且設定ReadIntervalTimeout ReadTotalTimeoutConstant參數為MAXULONG,則這個要求示為失敗並且回覆系統INVALID_PARAMETER的錯誤狀態碼。


如果事先不知道數據流的長度,一個讀取間隔逾時可以用作偵測輸入數據流的結尾。如果讀取請求使用這個技術,則STATUS_TIMEOUT的完成狀態通常表示這個請求已經成功的完成。

當輸入的數據間其時間間隔超出了ReadIntervalTimeout ,則發生一個讀取間隔逾時。逾時間隔由系統時鐘計算,而系統時鐘粒度(granularity)限制了逾時精準度的測量。其結果是,一個延時可能發生在一個系統時鐘tick的前後,更精確地說這取決於時間間隔的起始終止時間坐落於系統時鐘tick之間。逾時可能會有延遲的情況發生,當系統中斷處理其他裝置可能會造成系統時鐘中斷延遲。如果一個指定的逾時間隔接近或是小於系統時鐘ticks間的週期,則逾時可能無延遲並且立即發生。 <#1>

要更精準地測量較少的超時時間間隔,一種可能的方式是減少系統時鐘週期,但這樣做很可能會增加耗電量。此外,就算降低了系統時鐘週期也可能無法可靠地實現更精細的系統時鐘粒度,除非在平台上的各種驅動程序的中斷相關的處理可以保證不會耽誤系統時鐘中斷處理。

SERIAL_TIMEOUTS 結構類似COMMTIMEOUTS結構,COMMTIMEOUTS 結構用於使用者模式的SetCommTimeouts 以及 GetCommTimeouts

#1 T1計時器坐落於系統clock tick前,所以幾乎是不會延遲的。而T2計時器位於tick之間,有可能因為系統clock中斷,系統跑去處理其他裝置的事情,導致T2有些微的延遲。



2016年7月18日 星期一

關於TDI的一些想法

Windows上,基於某些應用,我們考量系統設計不會將所有的組件都設計在使用者模式上,即便在使用者模式我們有更多資源能比核心上使用更多且更簡單的API做到相同的功能。例如,一個虛擬裝置可能需要模擬遠端裝置的功能,而中介軟體只是負責命令的轉發,讓客戶端的軟體可以操控這個虛擬裝置如同操縱遠在300KM以外的裝置一般。那這個中介軟體可能是一個虛擬裝置的驅動程式,雙方的溝通可能透過TCP/IP網路。這裡的網路封包設計,當然可以像一般網路應用程式般,在使用層撰寫一個背景服務(微軟上稱為serviceLinux應該是稱作daemon)來操作winsock的網路操作。但不得不說這樣的設計並沒有很聰明,我會這樣說是因為微軟的系統架構中,底層驅動收到的命令前,通常會透過IO manager轉換為一個IRP的封包,一般應用程式blocking的寫法,上層發出命令後會進入等待狀態,直到系統回覆這個動作完成。驅動軟體(這裡只考慮單層驅動)會收到這個命令並且決定這個IRP是否完成。考慮上面的情形,背景程式負責網路處理,所以驅動收到命令,還要將命令格式(command類型),數據緩衝區,數據大小等資訊複製一份給上層知道,然後服務完成了網路通訊以後,在向核心通知命令完成,然後驅動在回覆給系統命令已經完成。光想到要花費那們多力氣撰寫程式還要面對後續的維護問題(上下層的同步問題),你還會堅持這樣的想法嗎?? 所以就我自己的經驗這樣的應用還是傾向將所有的架構都設計在核心層。所以學習網路核心介面就有其必要性了。

下一章就是有關TDI的介紹,主要是翻譯官網的文件。有關TDI驅動的部分,官網會討論三個主題,1. TDI Transports and Their Clients 2. TDI Routines, Macros, and Callbacks, 3. TDI Operations這部分要注意的是官網會在它的文件上一直提到,在Vista以後,像使用這類的NPI請用WSK(Windows Socket Kernel)來取代傳統的TDI寫法,然後想寫網路過濾驅動請使用WFP,應該是用來取代NDIS 中間層驅動。我目前的驅動在Windows7運行TDI的部分還是正常,看來微軟還是有保留TDI的運作模式,不過我自己也是有在研讀WSK的文件,畢竟如果未來想要支援IPV6的話,使用WSK實作上是相對簡單的。



2016年7月13日 星期三

Winsock Kernel - III

Winsock Kernel Overview


本章節提供Winsock Kernel(WSK)的概觀以及下列的主題:

Using Winsock Kernel Functions vs. Event Callback Functions

對於某些 socket 操作,Winsock Kernel(WSK)應用程式可以調用socket的 WSK 函式來執行操作或是實作以及致能事件回呼函式(event相關聯的操作發生時,WSK 子系統可以調用並且通知 WSK 應用程式)。舉例來說,要在連線導向(connection-oriented)socket上接收數據,WSK 應用程式可以選擇調用socketWskReceive或是實作一個WskReceiveEvent事件回呼函式。WSK 應用程式的需求決定了應用程式應該使用哪一種方式。整個 WSK 文檔裡提供了如何使用這兩種方法的例子。下面列出總結了每一種方法的一些要點。

Using Winsock Kernel Functions
  • WSK應用程式驅動socket操作,這意味著WSK應用程式控制何時會有socket操作的發生。透過WSK應用程式來簡化同步的需求。
  • WSK應用程式提供IRPssocket函式。這些IRPs會在WSK子系統內形成佇列,直到socket操作完成以後。更多有關IRPsWSK函式的訊息Using IRPs with Winsock Kernel Functions
  • WSK應用程式可以使用blocking socket操作,等待WSK子系統完成每一個IRP操作。
  • 為了能在連線導向socket上確保數據傳輸的高效性,WSK應用程式可能需要在某些情境下保留多個socket操作佇列,以防止在datagram sockets上傳入數據包被丟棄或是防止在listening sockets上傳入連線被丟棄。
  • WSK應用程式對於數據傳輸提供了數據緩衝區。這減少了數據需要複製的次數。然而,如果一個WSK應用程式保留了多個數據傳輸操作佇列,則應用程式必須對於每一個數據操作佇列提供給WSK子系統相對的數據緩衝區。因此,WSK應用程式可能需要額外的記憶體資源。

Using Event Callback Functions
  • WSK子系統驅動socket操作,這意味著WSK子系統透過 socket 的事件回呼函式來通知WSK應用程式。WSK應用程式可能需要更複雜的同步機制去處理事件回呼函式的異步特性。
  • WSK應用程式不會使用IRPs來進行socket操作。
  • WSK應用程式不需要對socket操作生成佇列。當socket事件發生時,WSK子系統會盡速的調用WSK應用程式的事件回呼函式。如果WSK 應用程式可以保持跟上事件回呼函式被調用的速率,則使用事件回呼函式可以提供更高的效率以及減少數據丟包或是傳入連線丟包的機會。
  • WSK子系統對於數據傳輸操作提供數據緩衝區。WSK應用程式必須在合理的時限內或是立即的釋放這些數據緩衝區,才能WSK子系統不會耗盡系統的記憶體資源。因此,WSK可能需要複製數據從 WSK子系統的緩衝區到它自己內部的數據緩衝區。
Note 以上列出來的未必詳盡。在一些特定的WSK應用程式上要選擇最佳的方式可能需要考慮其他可能的關鍵點。

Winsock Kernel Dispatch Tables


Winsock Kernel(WSK)socket object包含了指向提供調度表(provider dispatch table)結構的指標,調度表中包含了指向socket函式的指標。WSK應用程式呼叫了調度表內的函式來執行網路I/O操作。因為每一個WSK socket category支持不同的socket函式集合,所以在WSK Network Programming Interface (NPI)對於每一個WSK socket類別定義了不同的提供者調度表(provider dispatch table)結構。

如果WSK應用程式要在它建立的socket使用事件回呼函式,則它必須提供客戶調度表(client dispatch table)結構,客戶調度表包含函式指標指向socket的事件回呼函式。由於每一個WSK socket支援不同的事件回呼函式集合,所以WSK NPI對於每一種WSK socket定義了不同的客戶調度表(client dispatch table)集合。
Note  Basic sockets 不支援任何的事件回呼函式。因此,basic socket沒有定義任何的client dispatch table


Winsock Kernel Extension Interfaces

Winsock核心(WSK) Network Programming Interface (NPI)支援擴展接口。WSK子系統可以使用擴展接口在現有WSK NPI所定義的socket函式或事件回呼函式以外,另外擴充WSK socket的功能。每一個擴展接口通過NPI定義並且獨立於WSK NPI。現在的階段並沒有定義擴展接口。WSK應用程式可以透過SIO_WSK_REGISTER_EXTENSION IOCTL操作登記成為WSK子系統所支援的擴展接口。更多有關註冊擴展接口請看 Registering an Extension Interface

Using IRPs with Winsock Kernel Functions

Winsock Kernel (WSK) Network Programming Interface (NPI)對於網路I/O操作的異步完成使用了IRPs。每一個WSK函式提取IRP的指標作為參數。當WSK完成函式操作以後,WSK子系統會完成這個IRP

一個WSK應用程式用來傳遞給WSK函式的IRP可以透過以下任何一種方式發起。
  • WSK應用程式調用IoAllocateIrp函式來配置IRP。在這種情形下,WSK應用程式最少必須配置一個I/O stack locationIRP
  • WSK應用程式重新使用一個已完成的IRP(先前已配置)。在這種情形下,WSK必須調用IoReuseIrp來重新初始化這個IRP
  • WSK應用程式使用的IRP,這個IRP可能是由它更高層的驅動或是由I/O manager所傳遞給它的。在這種情形下,IRP最少需要一個可用的I/O stack locationWSK子系統使用。

WSK應用程式擁有一個IRP可以用來調用WSK函式以後,它可以為這個IRP設定IoCompletion常式,當WSK子系統完成這個IRP操作以後就可以調用這個常式。WSK應用程式可以調用IoSetCompletionRoutine函式來設定IoCompletion常式。依據如上三種IRP發起的方式,可以選擇要不要使用IoCompletion常式。 
  • 如果由WSK應用程式配置IRP,或是重新使用先前已經配置的IRP,則它在調用 WSK 函式以前必須對 IRP 設定一個 IoCompletion  常式。在這種情形底下,WSK 應用程式為了確保常式總是能 WSK 子系統所調用,所以WSK應用程式必須在IoSetCompletionRoutine內設定InvokeOnSuccess, InvokeOnError, 以及InvokeOnCancel這些參數為TRUE。此外,為了設定這個IRPIoCompletion常式總是回傳STATUS_MORE_PROCESSING_REQUIRED來重新取得IRP的操控權。在調用IoCompletion常式時,如果WSK應用程式完成了IRP的使用,則它在IoCompletion回傳結果給系統之前應該調用IoFreeIrp。如果WSK應用程式不釋放IRP則它可以重新使用這個IRP來調用其他的WSK函式。
  • 如果WSK應用程式使用來自於上層驅動或是I/O managerIRP,則在WSK函式完成後如果有必要通知WSK應用程式操作已經完成,則此情況下它應該要為IRP設定IoCompletion常式。如果WSK應用程式沒有設定IoCompletion常式,則當IRP完成時會按照正常的完成處理回傳給上層驅動或是I/O manager。如果WSK應用程式為IRP設定IoCompletion 常式,則IoCompletion常式可以回傳STATUS_SUCCESS或是STATUS_MORE_PROCESSING_REQUIRED。如果回傳STATUS_SUCCESS,則將正常處理IRP完成的動作。如果回傳STATUS_MORE_PROCESSING_REQUIRED,則WSK應用程式必須在WSK函式完成操作以後,自己調用IoCompleteRequest來完成IRPWSK應用程式絕不應該釋放來自於上層驅動或是I/O managerIRP

Note  如果WSK應用程式對於來自於高層的驅動或是IO管理員的IRP有設定IoCompletion 常式, 則在IoCompletion裡需要檢PendingReturned 是否為真,如果為真則需要呼叫IoMarkIrpPending 。更多訊息請參考 Implementing an IoCompletion Routine.

2016年7月10日 星期日

Winsock Kernel - II

Winsock Kernel Overview


本章節提供Winsock Kernel(WSK)的概觀以及下列的主題:

Winsock Kernel Architecture


下圖展示了Winsock Kernel(WSK)的架構。

WSK架構中的核心為WSK子系統。WSK 子系統為一個網路模組(network module),作為實作提供方的WSK Network Programming Interface (NPI)WSK子系統在他的下緣邊界提供各種的傳輸協議(transport protocols)


附加在WSK子系統的是WSK應用程式。WSK應用程式為核心模式的軟體模組,為了執行網路IO操作而實作客戶端的WSK NPI(這裡的”client”不應該與client-server系統中的名詞混淆)WSK子系統可以調用WSK客戶NPI來通知WSK應用程式一個異步事件發生。

WSK應用程式可以透過一組 WSK registration functions 來發現並且連接上WSK子系統。當WSK子系統為可用以及交換調度表(構成了WSK NPI的提供者和客戶端的實現)時,應用程式可以使用這些函式來動態的檢測。

WSK應用程式間可以輪流的使用 Network Module Registrar (NMR)來連接WSK子系統。更多資訊參考 Using NMR for WSK Registration and Unregistration

Winsock Kernel Objects

Winsock Kernel(WSK) Network Programming Interface (NPI)的設計是圍繞在兩個主要的對象類型: Client 以及 Socket

Client Object
客戶對象表示介於WSK應用程式與WSK子系統之間的附件(attachment)或是綁定(binding)。客戶對象透過結構 WSK_CLIENT表示。客戶對象的指標在WSK應用程式連接上WSK子系統的期間回傳。在客戶對象層級,WSK應用程式通過這個指標來操作所有的WSK函式。

Socket Object
Socket對象表示一個網路socket,可以用來使用網路I/Osocket對象透過結構WSK_SOCKET表示。當應用程式建立一個新的socket或是當應用程式接收一個連線請求時,WSK應用程式會得到一個socket對象的指標。WSK應用程式通過這個特定的指標處理所有WSK函式。


Winsock Kernel Socket Categories

Winsock Kernel(WSK) Network Programming Interface (NPI) 裡定義了四種不同的socket類型: basic sockets, listening sockets, datagram sockets以及connection-oriented sockets。每一個 WSK socket 類型擁有獨特的功能以及支援不同的 socket 功能集合。WSK 應用程式必須在它建立一個新的socket的時候指定它所屬的socket類型。每一種 WSK socket 的目的如下:

Basic Sockets
Basic sockets僅用於取得以及設定傳輸層堆疊 socket 選項或是操作socket I/O控制。Basic socket 不能綁定本地端的傳輸層位址以及不支援收發網路數據。

Listening Sockets
Listening socket用於監聽來自於遠端傳輸層位址的連線請求。Listening socket 的功能包含了 basic socket 所有的功能。

Datagram Sockets
Datagram socket用於收發數據。datagram socket 包含了 basic socket所有的功能。

Connection-Oriented Sockets
Connection-oriented socket用於在已建立的連線上收發網路數據。Connection-oriented socket包含了 basic socket 所有的功能。

Winsock Kernel Events

當某些 socket 事件發生,例如一個socket接收到新的數據或是當一個socket已經斷線時,Winsock Kernel (WSK)子系統可以異步通知 WSK 應用程式有事件發生。為了讓 socket 能夠異步的接收通知事件, WSK 應用程式必須實作對應的事件回呼函式並且激活這些事件回呼函式。

Note 對於WSK應用程式來說,實作或是使用事件回呼函式不是必需的。WSK應用程式可以透過調用合適的WSK socket函式來執行大部分的WSK socket操作。唯一有需要使用到事件回呼函式則是listening socket中的接收(conditional-accept)模式。更多有關使用WSK函式與使用事件回呼函式的優缺點參閱Using Winsock Kernel Functions vs. Event Callback Functions


Winsock Kernel Events

每一個 WSK socket category 支援不同的 socket 事件集合。

Basic sockets
Basic sockets 不支援任何的 socket 事件。

Listening sockets










* 僅適用在激活接收(conditional-accept)模式的listening socket更多有關條件接收模式的訊息參考 Listening for and Accepting Incoming Connections

Datagram sockets






Connection-oriented sockets

WSK應用程式建立一個 socket,此時 socket 的事件回呼函式預設為關閉。為了 WSK 子系統可以在事件發生的時候調用 socket 的事件回呼函式,WSK 應用程式必須致能 socket 事件回呼函式。更多有關致能與關閉事件回呼函式, Enabling and Disabling Event Callback Functions

如果 WSK 應用程式對一個 socket 註冊一個 extension interface,則擴展接口可能支援附加的事件。更多有關註冊一個擴展接口參閱 Registering an Extension Interface

WSK 子系統也可以通知 WSK 應用程式一個並沒有指定給特定socket的事件。為了讓 WSK 應用程式可以被接收這些事件,則 WSK 應用程式必須實作一個 WskClientEvent  事件回呼函式。WskClientEvent  總是致能並且不能被關閉。

WSK 應用程式的事件回呼函式不必等待其他 WSK 請求的完成或是其他WSK 事件的回呼函式。這個回呼函式可以動其他的 WSK 請求(假設它不需要花費太多時間在DISPATCH_LEVEL),但當回呼函式調用在 IRQL = PASSIVE_LEVEL 時,它絕不會等待請求事件完成。

2016年7月5日 星期二

Winsock Kernel - I

Introduction to Winsock Kernel

Winsock Kernel (WSK)為核心模式的 Network Programming Interface (NPI)。有了 WSK,核心模式的軟體模組可以使用與使用者模式 Winsock2 相同的 socket 編程概念來操作網路 IOWSK NPI 支援相似的 socket 操作,例如 socket 的建立,綁定,連接,以及數據傳輸(發送與接收)。然而,雖然 WSK NPI 支持與使用者模式 Winsock2 相似的 socket 編程概念,但是它仍然擁有獨特的特點,例如異步 I/O 利用 IRPs 以及回呼事件來提升效能。

針對Windows Vista以及之後的版本,由於效能的改善以及更簡易的編程方式,因此核心網路模組使用 WSK 取代 TDI。在 Vista 之後,實作過濾式驅動應該使用 Windows Filtering Platform 實作 TDI client 應該使用WSK

Note  在微軟Vista以及未來的版本可能不會支持 TDI 的方式,未來要利用 Windows Filtering Plarform 以及 Winsock Kernel 來取代傳統的方式。