《Windows via C/C++》學習筆記 —— 裝置I/O之“非同步裝置I/O請求”

來源:互聯網
上載者:User

  非同步裝置I/O適用於大資料量和高效能的場合,比如伺服器。

  要使用非同步裝置I/O,在調用CreateFile來開啟或建立一個裝置的時候,讓參數dwFlagsAndAttributes包括FILE_FALG_OVERLAPPED,這意味著想讓開啟的裝置可以被非同步訪問。

  為了發送一個I/O請求給一個裝置,也就是讓一個I/O請求進入I/O隊列,你可以使用ReadFile和WriteFile這兩個函數:

BOOL ReadFile(
   HANDLE      hFile,
   PVOID       pvBuffer,
   DWORD       nNumBytesToRead,
   PDWORD      pdwNumBytes,
   OVERLAPPED* pOverlapped);BOOL WriteFile(
   HANDLE      hFile,
   CONST VOID  *pvBuffer,
   DWORD       nNumBytesToWrite,
   PDWORD      pdwNumBytes,
   OVERLAPPED* pOverlapped);

 

  當這兩個函數被呼叫,系統通過第一個參數hFile,來查看該控制代碼指明的裝置在開啟的時候是否使用了FILE_FLAG_OVERLAPPED,如果使用了,這兩個函數執行非同步裝置I/O,反之,則執行同步裝置I/O。當使用非同步I/O方式的時候,在調用這兩個函數的時候,可以將NULL傳遞給pdwNumBytes參數,因為不知道何時裝置I/O完成,因此使用這個參數沒有多大意義。

  注意最後一個參數,是一個OVERLAPPED結構的指標:

typedef struct _OVERLAPPED {
   DWORD  Internal;     // 錯誤碼(出口參數,返回)
   DWORD  InternalHigh; // 傳輸的資料大小,以位元組為單位(出口參數,返回)
   DWORD  Offset;       // 低32位位移量(入口參數,輸入)
   DWORD  OffsetHigh;   // 高32位位移量(入口參數,輸入)
   HANDLE hEvent;       // 事件核心物件控點(入口參數,輸入)
} OVERLAPPED, *LPOVERLAPPED;

 

  該結構包含5個成員,其中的3個——Offset、OffsetHigh、hEvent應該在調用ReadFile和WriteFile之前被初始化,另外的2個——Internal、InternalHigh會在I/O完成的時候被裝置驅動程式所設定,下面細述一下:

  • Offset、OffsetHigh —— 在使用非同步裝置I/O來操縱“檔案裝置”的時候,檔案讀寫指標被忽略,此時I/O的位移量由OVERLAPPED結構中的Offset和OffsetHigh決定。另外,在“非檔案裝置”中,這兩個成員不會被忽略,一般必須要設定為0。
  • hEvent —— 一個事件核心物件控點,可以有多種使用方法,後面會講到。
  • Internal —— 儲存I/O錯誤碼,當你發送一個I/O請求的時候,該參數被設定為STATUS_PENDING,指明沒有錯誤發生,因為操作還沒有開始。你可以使用HasOverlappedIoCompleted宏來查看一個非同步裝置I/O是否完成,該結構接受一個OVERLAPPED結構指標,如果I/O請求完成返回TRUE。如果I/O請求仍然沒有開始,返回FALSE。
  • InternalHigh —— 非同步I/O請求完成的時候,該成員裡儲存了傳送資料量的位元組數。

  當非同步I/O請求完成之後,你可以接受到一個OVERLAPPED結構的指標。一般可以讓一個C++類從OVERLAPPED結構派生,類中加入一些其他資訊,使得更容易處理。然後當使用ReadFile和WriteFile函數的時候,可以傳遞這個C++類對象的指標,當I/O完成之後,接受該結構的時候,可以將其轉換為C++類對象,不但可以獲得其5個成員,還可以獲得類中的其他資訊。

 

  使用非同步裝置I/O的時候,要注意以下三點:

  1、裝置驅動程式不一定會按照一個“先進先出”(FIFO)的順序來處理裝置I/O請求,因此如下編碼不會保證先讀後寫:

OVERLAPPED o1 = { 0 };
OVERLAPPED o2 = { 0 };
BYTE bBuffer[100];
ReadFile (hFile, bBuffer, 100, NULL, &o1);   //讀
WriteFile(hFile, bBuffer, 100, NULL, &o2);   //寫

 

  2、以非同步方式進行I/O請求的是,驅動程式可能會選擇同步的方式。當你讀取一個檔案的時候,如果系統發現讀取的資料在cache中,且資料有效,那麼該I/O請求就不需要驅動程式了,而是直接將cache中的資料複製到你的緩衝區中。驅動在某些操作上一直使用同步方式,比如在NTFS格式上的檔案壓縮,擴充檔案長度,添加檔案資訊等。

  這個時候,如果ReadFile和WriteFile返回非0值,則表明它以同步方式進行。如果返回FLASE,說明發生了一個錯誤,這個時候可以通過GetLastError來取得資訊,如果返回ERROR_IO_PENDING,則說明I/O請求成功提交,但沒有完成。

 

  3、資料緩衝區和OVERLAPPED結構在非同步I/O請求完成之前不能被移動或釋放。當裝置驅動準備處理你的I/O請求的時候,它將資料傳送到pvBuffer參數對應的地址上去,並訪問OVERLAPPED結構中的Offset等成員。當I/O請求完成之後,裝置驅動更新OVERLAPPED結果中的Internal和InternalHigh成員。因此,不能在I/O請求完成之前移動或釋放資料緩衝區和OVERLAPPED結構,否則,記憶體資料會被破壞,而且在每次調用ReadFile或WriteFile的時候,都必須分配一個單獨的OVERLAPPED結構。

  比如,下面的代碼是有BUG的:

VOID ReadData(HANDLE hFile)
{
   OVERLAPPED o = { 0 };
   BYTE b[100];
   ReadFile(hFile, b, 100, NULL, &o);
}  //此時緩衝區b和OVERLAPPED結構o都被釋放

 

  你可以將一個裝置I/O請求清除佇列,即撤消該請求。可以有如下方法:

 

  1、在一個線程中調用CancelIo函數,可以取消該線程發送給指定裝置有關的所有I/O請求,除了指定的裝置是“I/O完成連接埠”。

BOOL CancelIo(HANDLE hFile);     //參數是裝置物件控點

 

  2、取消與一個裝置有關的所有I/O請求,關閉這個裝置控制代碼即可。

 

  3、當一個線程結束,系統自動取消該線程發送的所有I/O請求,除了發送給“I/O完成連接埠的”I/O請求。

 

  4、如果想取消某一個特定的I/O請求,可以使用CancelIoEx函數,傳遞一個OVERLAPPED結構指標給它:

BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED pOverlapped);

 

  該函數可以跨線程使用,也就是在T1線程內發送的I/O請求,可以在T2線程內通過該函數結束之。因為每個I/O請求都需要一個唯一的OVERLAPPED結構,所以該OVERLAPPED結構就標識了一個I/O請求。如果傳遞NULL給CancelIoEx函數的第2個參數,那麼就會取消與hFile對應的裝置的所有I/O請求。

 

  取消一個I/O請求,該I/O請求會結束,同時錯誤碼被設定為ERROR_OPERATION_ABORTED。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.