題記:最近在學習Windows SDK編程,打算在這裡貼出自己的學習總結和心得與大家交流,主要參考資料來自<Windows via C/C++ 5th>和<Programming Windows>。我盡量用英文術語來表達技術概念,方便大家尋找其它資料。第一篇從非同步IO(Asynchronous IO)說起,以檔案IO作為代表。
非同步IO是現代作業系統必不可少的特性,它讓寶貴的CPU計算資源不會浪費在等待慢速IO上。它的行為方式很直觀,使用者線程在發送IO請求(Issue IO Request)後不用一直掛起,直到IO完成,而是直接返回繼續執行其它任務。在裝置驅動(Device Driver)完成IO請求後,會通知使用者線程資料轉送已完成,可以進行相關操作。Windows非同步IO的過程主要有兩步:1)向裝置驅動發送IO請求,2)裝置驅動在完成IO請求後通知使用者已完成資料轉送,即完成通知(Completion Notification)。這一篇主要介紹第一步:發送IO請求。
Windows SDK中關於檔案非同步IO的操作,主要涉及這樣幾個基本函數CreateFile,ReadFile,WriteFile。
1,HANDLE CreateFile(<br /> PCTSTR pszName,<br /> DWORD dwDesiredAccess,<br /> DWORD dwShareMode,<br /> PSECURITY_ATTRIBUTES psa,<br /> DWORD dwCreationDisposition,<br /> DWORD dwFlagsAndAttributes,<br /> HANDLE hFileTemplate);
此函數用於開啟一個裝置,當然包括檔案。當準備發送一個非同步IO請求時,必須在dwFlagsAndAttributes參數中指定FILE_FLAG_OVERLAPPED標誌,這樣告訴系統你想要以非同步方式訪問裝置。
2,BOOL ReadFile(<br /> HANDLE hFile,<br /> PVOID pvBuffer,<br /> DWORD nNumBytesToRead,<br /> PDWORD pdwNumBytes,<br /> OVERLAPPED* pOverlapped);</p><p>BOOL WriteFile(<br /> HANDLE hFile,<br /> CONST VOID *pvBuffer,<br /> DWORD nNumBytesToWrite,<br /> PDWORD pdwNumBytes,<br /> OVERLAPPED* pOverlapped);
大家都會使用這兩個函數進行同步IO,比如:
#include <windows.h><br />#include <stdio.h><br />int main()<br />{<br />HANDLEhFile = NULL;<br />BYTEbuff[1024];<br />DWORDdwRead = 0;</p><p>hFile = CreateFile(TEXT("data.txt"), GENERIC_READ, FILE_SHARE_READ,<br /> 0, OPEN_EXISTING, 0, NULL);</p><p>ReadFile(hFile, buff, 1024, &dwRead, NULL);</p><p>printf("%s", buff);</p><p>return EXIT_SUCCESS;<br />}
這裡的pOverlapped參數設為NULL。而在進行非同步IO時,必須通過這個參數給這個IO請求指定一個OVERLAPPED結構,資料成員如下:
typedef struct _OVERLAPPED {<br /> DWORD Internal; // [out] Error code<br /> DWORD InternalHigh; // [out] Number of bytes transferred<br /> DWORD Offset; // [in] Low 32-bit file offset<br /> DWORD OffsetHigh; // [in] High 32-bit file offset<br /> HANDLE hEvent; // [in] Event handle or data<br />} OVERLAPPED, *LPOVERLAPPED;
其中:
Internal 作為輸出參數,返回這次IO請求的錯誤碼。
InternalHigh 作為輸出參數,返回這次IO請求的讀寫的位元組數。
Offset和OffsetHigh 作為輸入參數,指定此次IO請求的起始位移。這個與同步IO不同:對於每個檔案核心對象(File Kernel Object),所有IO請求都依次執行,不能重疊(Overlapped名稱的由來),每次IO的起始位移由檔案核心對象的檔案指標指定。但在非同步IO中,多個IO請求可能重疊進行,這樣,檔案核心對象中的檔案指標是沒有意義的,所以每個IO請求必須在Overlapped結構中單獨指定起始位移。
hEvent 作為輸入參數,用於完成通知(Completion Notification)。以後會詳細講到。
由上面的功能可看出,Overlapped提供的資訊幾乎覆蓋了一個IO請求的方方面面,事實上,每一個IO請求都對應一個Overlapped,它是使用者與裝置驅動通訊的通道。因此,在整個IO請求完成前,Overlapped結構必須一直有效。
使用者在用ReadFile/WriteFile向裝置驅動發送IO請求後,必須仔細對函數傳回值進行檢查:
1)若返回非0值,表示IO請求立即完成並返回,此時IO請求並沒有進入請求隊列。這對非同步IO請求是完全可能的,因為系統以前可能剛好緩衝(cache)了使用者此時請求的內容,所以可以可以馬上完成請求。
2)若返回FALSE,則必須馬上調用GetLastError進一步判斷。如果GetLastError返回ERROR_IO_PENDING,表明此IO請求成功加入請求隊列。如果返回其它值,表明此IO請求不能被加入請求隊列,下面是常見的加入請求失敗的傳回值:
ERROR_INVALID_USER_BUFFER/ERROR_NOT_ENOUGH_MEMORY 表明隊列已滿,無法加入新的請求。參考代碼:
#include <windows.h><br />#include <stdio.h><br />int main()<br />{<br />HANDLEhFile = NULL;<br />BYTEbuff[1024];<br />OVERLAPPEDol = {0};<br />BOOLbRet = FALSE;<br />DWORDdwCode = 0;</p><p>hFile = CreateFile(TEXT("data.txt"), GENERIC_READ, FILE_SHARE_READ,<br />0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);</p><p>ol.Offset = 16; //Read data from file starting at byte 16<br />bRet = ReadFile(hFile, buff, 1024, NULL, &ol);</p><p>if (bRet) //IO request is completed immediately<br />{<br />}<br />else if ((dwCode = GetLastError()) == ERROR_IO_PENDING) //IO request is queued successfully<br />{<br />}<br />else<br />{<br />//Error Handling here<br />}</p><p>return EXIT_SUCCESS;<br />}
我們前面說過,Overlapped結構中的Internal成員是作為此次IO請求的錯誤碼返回,所以我們可以通過檢查此變數判斷目前IO請求的狀態。我們在發送IO請求後,如果此請求被成功排入佇列,Overlapped結構中的Internal變數會被設為STATUS_PENDING。事實上Windows為我們提供了一個宏:
#define HasOverlappedIoCompleted(pOverlapped) /<br /> ((pOverlapped)->Internal != STATUS_PENDING)
Overlapped提供幾乎所有我們需要的資訊,這也充分說明了它是使用者與裝置驅動進行通訊的通道。