本章回答了如下幾個問題:
◆ 什麼是Overlapped I/O?為什麼需要Overlapped I/O?如何讓數據傳輸支持Overlapped I/O?
◆ 數據傳輸結束後,Win32提供了哪些方式對用戶進行通告,以便進行適當的善後?
◆ 影響線程優先級的因素有哪些?如何獲取或設置進程線程優先級?優先級的改變容易帶來哪些問題?又該如何應對?
◆ 什麼是被激發的文件句柄?什麼是被激發的事件?什麼是異步進程調用(APCs)?這些方式各是如何實現Overlapped I/O的?各有何優缺點?
◆ 使用Overlapped I/O的初衷是使“受制於I/O的程序”中獲得高效率。但是否是各種情況下Overlapped I/O都能提高系統效率嗎?
◆ 什麼是I/O completion port?這個機制是怎麼工作的?有什麼優點?為什麼說此方式能夠很好地支持scalable(可升級的)系統?而對於工作於此模式下的文件,從提高系統效率考慮,怎樣才能避免無謂的completion packet通告呢?
什麼是Overlapped I/O?
Overlapped I/O,常被設計為多線程處理,以便在一個“受制於I/O的程序”中獲得高效率。
利用Win32所謂的overlapped I/O特性,可以讓I/O操作並行處理,並且當一個I/O完成時,程序會收到一個通告。有些系統把這個特性稱為非阻塞I/O,或異步I/O。
overlapped I/O是Win32的一項技術,你可以要求操作系統為你傳送數據,並且傳送完畢後通知你。這項技術使你的程序在I/O進行中仍然能夠繼續處理事務。事實上,操作系統內部正是以線程來完成overlapped I/O。這樣,你可以獲得線程的所有利益,而不需付出痛苦代價。
在Windows 95環境下,Overlapped I/O使用有些限制。它不支持磁盤或光盤中的文件操作。
Win32文件操作函數
HANDLE CreateFile(
LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes,// pointer to security attributes
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to copy
);
BOOL ReadFile(
HANDLE hFile, // handle of file to read
LPVOID lpBuffer, // pointer to buffer that receives data
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read
LPOVERLAPPED lpOverlapped // pointer to structure for data
);
BOOL WriteFile(
HANDLE hFile, // handle to file to write to
LPCVOID lpBuffer, // pointer to data to write to file
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
LPOVERLAPPED lpOverlapped // pointer to structure for overlapped I/O
);
BOOL CloseHandle(
HANDLE hObject // handle to object to close
);
一些說明:
CreateFile()可處理的對象包括:files、pipes、mailslots、communication resource、disk device(Windows NT only)、consoles、directories(open only)。
如果要使用異步I/O,CreateFile()時設置dwFlagsAndAttributes含FILE_FLAG_OVERLAPPED標志,通知操作系統對此文件的訪問采取異步I/O模式。
由於異步I/O模式下,可以在同一時間讀(或寫)文件的許多部分,所以沒有“目前的文件位置”這一概念。每次讀寫都必須包含其文件位置。
OVERLAPPED結構
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
說明:
Internal
通常它被保留。然而,當GetOverlappedResult()返回FALSE,GetLastError()返回ERROR_IO_PENDING時,這個欄位將內含一個視系統而定的狀態。
InternalHigh
通常它被保留。然而,當GetOverlappedResult()返回TRUE時,這個欄位將內含“被傳輸的數據長度”。
Offset
定義文件開始讀(或寫)的開始位置,以字節為單位。該偏離位置從文件頭開始起算。如果目標設備不支持文件位置(比如管道),此欄忽略。
OffsetHigh
64位的文件偏離量,較高的32位。如果目標設備不支持文件位置(比如管道),此欄忽略。
hEvent
一個手動事件。當overlapped I/O完成後即被激發。ReadFileEx()和WriteFileEx()忽略這個欄位,彼時它可能用來傳輸一個用戶自定義的指針。
注意: OVERLAPPED結構的生命周期應超越ReadFile()和WriteFile()函數。
被激發的File Handles
注意,此方式在Windows95/98下不被支持。詳看MSDN中關於GetOverlappedResult()函數的說明。
在Windows NT下,最簡單的overlapped I/O類型,是使用它自己的文件句柄作為同步機制。大致流程如下:
BOOL GetOverlappedResult(
HANDLE hFile, // handle to file, pipe, or comm device
LPOVERLAPPED lpOverlapped, // pointer to overlapped structure
LPDWORD lpNumberOfBytesTransferred, // pointer to actual bytes count
BOOL bWait // wait flag
);
說明:如果bWait設置為FALSE,如果overlapped I/O還沒完成,函數返回FALSE,調用GetLastError()會返回ERROR_IO_INCOMPLETE。所以,調用GetOverlappedResult()時如果bWait設置為FALSE,可即時查詢overlapped I/O的狀態。
舉例:
hFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return -1;
}
// Initialize the OVERLAPPED structure
memset(&overlap, 0, sizeof(overlap));
overlap.Offset = 1500;
// Request the data
rc = ReadFile( hFile,buf,READ_SIZE,&numread,&overlap);
if (rc)
{
// The data was read successfully
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
VERIFY(WAIT_OBJECT_0 == WaitForSingleObject(hFile, INFINITE));
rc = GetOverlappedResult(hFile, &overlap,&numread,FALSE);
}
else
{
// Something went wrong
}
}
CloseHandle(hFile);
說明:
被激發的事件對象
使用文件句柄作為激發機制,有一個明顯的限制:如果多個線程對同一個文件進行操作,由於只有一個相同的handle,對於每個可能進行的overlapped操作都調用GetOverlappedResult()查看操作是否完成,這將不是一個很有效率的做法——因為很多的時候並不是自己所期待的操作完成了。另外,Windows95/98下不可以使用文件句柄作為激發機制。
OVERLAPPED結構中的最後一個欄位是一個事件句柄。如果使用文件句柄作為激發對象,可將該位設置為NULL。如果該位被設定為一個事件對象時,系統核心會在overlapped操作完成後,自動設置此事件為激發。
由於每個overlapped操作都有它獨一無二的OVERLAPPED結構,每個結構都有它獨一無二的事件對象,用以代表該操作。
注意:OVERLAPPED結構中的事件必須是手動事件。否則,如果事件為自動事件,由於系統核心可能會在你有機會等待該事件前就激發它,而自動事件的激發狀態是不能保留的,於是事件遺失,這將導致你的等待永遠無法返回。
使用手動事件配搭overlapped I/O,就可以對同一個文件發出多個讀取操作和多個寫入操作,每個操作都有自己的事件對象。然後調用wait…()函數等待其中之一或全部完成。
異步過程調用(Asynchronous Procedure Calls,APCs)
使用overlapped I/O配搭手動事件,會產生兩個問題:
異步過程調用可解決此問題。此時只要使用“Ex”版的ReadFile()和WriteFile()。這兩函數可額外指定一個參數,定義一個callback函數。當一個overlapped I/O完成時,系統調用該callback函數。這個callback函數被稱為I/O completion routine,因為,系統是在一個特定的overlapped I/O完成後調用它的。
但是,需要注意的是,一個特定的overlapped I/O完成後,Windows並不會貿然中斷你的程序,然後調用你所提供的callback函數。那顯然可能會帶來新的問題。只有線程說“好,現在是一個安全時機”時系統才會調用你的callback函數。以Windows的說法,只有線程處於所謂的“alertabe”狀態,回調函數才會被執行,否則對I/O completion routine的調用會被暫時擱置下來。因此,當一個線程終於處於“alertabe”狀態時,可能有一堆儲備的APCs等待被處理。
如果線程因為以下5個函數而處於等待狀態,而其“alertabe”標志被設置為TRUE,則該現程就是處於“alertabe”狀態:
DWORD SleepEx(
DWORD dwMilliseconds, // time-out interval in milliseconds
BOOL bAlertable // early completion flag
);
DWORD WaitForSingleObjectEx(
HANDLE hHandle, // handle to object to wait for
DWORD dwMilliseconds, // time-out interval, in milliseconds
BOOL bAlertable // return to execute I/O completion routine if TRUE
);
DWORD WaitForMultipleObjectsEx(
DWORD nCount, // number of handles in handle array
CONST HANDLE *lpHandles, // points to the object-handle array
BOOL fWaitAll, // wait flag
DWORD dwMilliseconds, // time-out interval in milliseconds
BOOL bAlertable // alertable wait flag
)
DWORD MsgWaitForMultipleObjectsEx(
DWORD nCount, // number of handles in handle array
LPHANDLE pHandles, // pointer to an object-handle array
DWORD dwMilliseconds, // time-out interval in milliseconds
DWORD dwWakeMask, // type of input events to wait for
DWORD dwFlags // wait flags
);
DWORD SignalObjectAndWait(
HANDLE hObjectToSignal, // handle to object to signal
HANDLE hObjectToWaitOn, // handle to object to wait for
DWORD dwMilliseconds, // time-out interval in milliseconds
BOOL bAlertable // alertable flag
);
只有使用上面這些函數進行等待ReadFileEx()或WriteFileEx()操作,回調函數才會被執行。
回調函數解釋
VOID CALLBACK FileIOCompletionRoutine(
DWORD dwErrorCode, // completion code
DWORD dwNumberOfBytesTransfered, // number of bytes transferred
LPOVERLAPPED lpOverlapped // pointer to structure with I/O information
);
dwErrorCode:0表示操作完成,ERROR_HANDLE_EOF表示操作到文件尾
dwNumberOfBytesTransfered:真正被傳輸的數據字節數
lpOverlapped:指向OVERLAPPED結構,由開啟overlapped I/O操作的函數提供。由於APCs時OVERLAPPED結構中hEvent欄位不需要用來放置一個事件句柄,此欄程序員可自由運用,比如用作回調函數的入口參數。
對文件進行Overlapped I/O的缺點
在Windows NT測試時發現,Windows NT似乎是以“I/O請求”的大小來決定是否進行overlapped I/O。如果數據量較小,系統實際上總是采取非overlapped方式進行文件讀取;數據量略大些,采取Overlapped I/O其實比單純調用ReadFile()並無優越性,相反地,效率降低。如果文件數據量比較大,Overlapped I/O才能凸顯其優越性。