【讀書筆記】同步設備I/O與異步設備I/O


同步IO

最方便和常用的對設備數據進行讀寫的函數時ReadFile和WriteFile。
打開設備時如:CreatFile、WSASocket。沒有指定FILE_FLAG_VERLAPPED標誌,則執行同步IO操作。


異步IO

打開設備時如:CreatFile、WSASocket。指定FILE_FLAG_VERLAPPED標誌,則執行異步IO操作。

當調用ReadFile()或WriteFile()時,函數會檢查hFile參數標識的設備是否是用FILE_FLAG_OVERLAPPED標誌打開的。如果是,則執行異步I/O。


OVERLAPPED結構

執行異步IO時,必須在ReadFile()或WriteFile()參數中的pOverlapped參數中傳入一個已經初始化的OVERLAPPED結構。

OVERLAPPED結構包含以下成員:

1、偏移量:爲了避免對同一個對象進行多個異步調用時出現混淆,所有異步IO請求必須指定偏移量。非文件設備會忽略此成員。

2、hEvetn成員:用來接收I/O完成通知的4種方式的一種會用到這個成員。

3、Internal成員:保存IO請求錯誤碼

4、InternalHight成員:保存已傳輸字節數。

使用技巧:異步IO請求完成時,會收到一個OVERLAPPED結構的地址,它就是我們發出請求時使用的那個。故可以派生OVERLAPPED類,用於傳輸其他一些附加信息。


異步IO注意事項

  • 1 . 設備驅動程序不是先入先出處理IO請求的

  • 2 . 當將一個異步IO請求添加到隊列中的時候,設備驅動程序可能選擇同步的方式處理請求!如當我們讀取文件數據時,如果數據已經在緩存中,系統就不會將請求添加到設備驅動程序的隊列中,而會將高速緩存中的數據複製到我們的額緩存中,從而完成這個IO操作。

如果請求的IO操作是以同步的方式執行的,那麼ReadFile()/WriteFile()會返回非零值。如果IO是以異步方式執行的執行,或者發生了錯誤,則返回FALSE(0)。

  • 3 .異步IO請求完成之前,一定不能移動或者銷燬發出請求時使用的數據緩存和OVERLAPPED結構。設備驅動程序會使用數據緩存和OVERLAPPED結構。我們必須爲每個IO請求分配、初始化一個獨立的OVERLAPPED結構。

接收I/O請求完成通知

設備驅動程序如何通知我們IO請求已經完成?
windows提供了4種方法:

  • 觸發設備內核對象
  • 觸發事件內核對象
  • 使用可提醒I/O
  • 使用I/O完成端口
技術 特點 優缺點
觸發設備內核對象 允許一個線程發出I/O請求,另一個線程對結果進行處理 當向一個設備發出多個I/O請求,只要任何一個I/O請求完成時都會被觸發,卻沒辦法區別是哪個請求的完成觸發了內核對象。
觸發事件內核對象 允許一個線程發出I/O請求,另一個線程對結果進行處理 每個請求都通過pOverlapped與一個事件相關聯,允許我們向一個設備同時發出多個I/O請求。
使用可警告I/O 發出I/O請求的線程必須對結果進行處理 沒有負載均衡,伸縮性不好
使用I/O完成端口 允許一個線程發出I/O請求,另一個線程對結果進行處理 具有高度的伸縮性和最佳的靈活性

觸發設備內核對象

ReadFile()/WriteFile()函數在將I/O請求添加到隊列之前,會將設備內核對象設爲未觸發狀態。當設備驅動程序完成了請求之後,驅動程序會將設備內核對象設爲觸發狀態。

線程可以使用WaitForSingleObject()或者WaitForMultipleObject()檢查異步IO是否完成。

WaitForMultipleObject()可指定參數,設置:1 等待指定的內核對象中的一個被觸發爲止,2 等待指定的內核對象全部被觸發爲止

問題:

向一個設備同時發出多個I/O請求的時候,這種方法是不能用的,因爲等待函數中等待的是同一個內核對象,只要任何一個I/O請求完成時都會被觸發,卻沒辦法區別是哪個請求的完成觸發了內核對象


觸發事件內核對象

OVERLAPPED結構的一個成員hEvent用來標識一個事件內核對象。

我們必須通過CreateEvent創建這個事件對象,當設備驅動程序完成IO請求時,會檢查OVERLAPPED結構成員hEvent是否爲NULL,若不是,則會觸發。此時,驅動程序仍然會觸發設備內核對象。

允許我們向一個設備同時發出多個I/O請求的時候。(因爲每個請求都通過pOverlapped與一個事件相關聯)。

如果想要同時執行多個異步IO請求,必須爲每個請求創建不同的事件對象,並初始化每個請求的OVERLAPPED結構。再調用ReadFile()/WriteFile()。

我們只需在線程中調用WaitForMultipleObject()並傳入與每個待處理的IO請求的OVERLAPPED結構關聯的事件句柄。

通過這種方式,可以同時執行多個異步IO操作並使用同一個設備對象。


可提醒I/O

當系統創建一個線程的時候,會同時創建一個與線程相關聯的隊列。這個隊列被稱爲異步過程調用 APC(asynchronous procedure call)隊列。

當發出一個IO請求的時候,可以告訴設備驅動程序在調用線程的APC隊列中添加一項。

爲了將IO完成通知添加到線程的APC隊列中,需要調用ReadFileEx()和WriteFileEx()函數。

*Ex函數要求傳入一個回調函數的地址。回調函數被稱爲完成函數。

當線程處於可提醒狀態的時候,系統會馬上檢查它的APC隊列,對隊列中的每一項,系統會讓線程調用完成函數。

當IO請求完成的時候,系統會將它們添加到發出請求的線程的PAC隊列中,回調函數不會馬上被執行,這是因爲線程可能在處理重要的事情,不能被打斷。爲了對線程的APC隊列中的項進行處理,線程可通過調用等待內核對象函數將自己設置爲可提醒狀態。

如果APC隊列爲空,線程會被掛起,直到APC隊列非空,或者所等待的內核對象被出觸發。如果APC隊列出現了一項,系統喚醒線程並調用回調函數,然後返回,不會再次進入睡眠狀態等待內核對象

可理解爲:設備驅動程序完成IO請求後,系統提醒發出IO請求的線程去繼續完成後續操作。

問題:

發出I/O請求的線程必須對結果進行處理,因爲這是通過線程的APC隊列來實現的,而APC隊列是線程獨有的。


I/O完成端口

創建IO完成端口

CreateIoCompletionPort()

關聯IO完成端口與設備

CreateIoCompletionPort()

兩個函數是一樣的!只是調用時傳入的參數不一樣。創建完成端口時,可以指定同一時間最多能有多少線程處於可運行狀態。

設備必須是用FILE_FLAG_OVERLAPPED標誌打開的。即異步IO屬性。

獲取IO請求完成信息

GetQueuedCompletionStatus()

線程池中的所有線程應該執行同一個函數。
函數中,不斷循環。
循環內部,線程調用GetQueuedCompletionStatus()將自己切換到睡眠狀態,直到指定的完成端口的IO完成隊列中出現一項,或者等待超時爲止。

調用了GetQueuedCompletionStatus()函數後,線程會得到已完成IO項中的所有信息。可以繼續後續處理操作。

  • 是否自動創建線程?

否,需要程序員自行創建線程,並向線程傳入IO完成端口的句柄。該線程爲了確認I/O的完成會調用GetQueuedCompletionStatus()函數,並傳入IO完成端口的句柄。線程就會得到指定IO完成端口中已完成IO項中的所有信息。

任何線程都能調用GetQueuedCompletionStatus()函數,但實際得到I/O完成信息的線程數不會超過創建CP對象時指定的最大線程數。

理解:

在可提醒I/O中,設備驅動程序將將完成IO請求的信息放入發出IO請求的線程APC隊列中,讓發出請求的線程執行對IO結果的處理。

在I/O完成端口中,所有與完成端口關聯了的設備,會將完成IO請求信息統一放入完成端口的線程池任務隊列中,由完成端口調用線程池中的空閒線程對IO結果進行處理。


實例參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章