Alertable I/O
1. Alertable I/O 的概念
Alertable I/O 是异步I/O操作完成之后,对其结果进行处理的一种机制,为了理解Alertable I/O,读下面的文章(来自MSDN)
Alertable I/O is the method by which application threads process asynchronous I/O requests only when they are in an alertable state.
To understand when a thread is in an alertable state, consider the following scenario:
1. A thread initiates an asynchronous read request by calling ReadFileEx with a pointer to a callback function.
2. The thread initiates an asynchronous write request by calling WriteFileEx with a pointer to a callback function.
3. The thread calls a function that fetches a row of data from a remote database server.
In this scenario, the calls to ReadFileEx and WriteFileEx will most likely return before the function call in step 3. When they do(ReadFileEx and WriteFileEx完成数据的读写), the kernel places the pointers to the callback functions on the thread's Asynchronous Procedure Call (APC) queue. The kernel maintains this queue specifically to hold returned I/O request data until it can be processed by the corresponding thread(调用ReadFileEx and WriteFileEx函数的那个线程).
When the row fetch is complete and the thread returns from the function, its highest priority is to process the returned I/O requests on the queue by calling the callback functions. To do this(为了使kernel维护的APC队列中的CALLBACK函数得到调用,实际上就是指对I/O操作结果进行处理), it (线程)must enter an alertable state. A thread can only do this by calling one of the following functions with the appropriate flags:
SleepEx
WaitForSingleObjectEx
WaitForMultipleObjectsEx
SignalObjectAndWait
MsgWaitForMultipleObjectsEx
When the thread enters an alertable state(也就是thread调用以上的函数并设置适合的标志,线程调用了这些函数并不立即返回,并且此时线程进入
Alertable状态), the following events occur:
1. The kernel checks the thread's APC queue. If the queue contains callback function pointers, the kernel removes the pointer from the queue and sends it to the thread.
2. The thread executes the callback function.
3. Steps 1 and 2 are repeated for each pointer remaining in the queue.
4. When the queue is empty, the thread returns from the function that placed it in an alertable state.(当kernel维护的APC队列变为空队列时,线程就从把该线程变成可提醒状态的函数(SleepEx等)中返回)
In this scenario, once the thread enters an alertable state it will call the callback functions sent to ReadFileEx and WriteFileEx, then return from the function that placed it in an alertable state.
If a thread enters an alertable state while its APC queue is empty, the thread's execution will be suspended by the kernel until one of the following occurs:
1. The kernel object that is being waited on becomes signaled.
2. A callback function pointer is placed in the APC queue.
A thread that uses alertable I/O processes asynchronous I/O requests more efficiently than when they simply wait on the event flag in the OVERLAPPED structure to be set, and the alertable I/O mechanism is less complicated than I/O completion ports to use. However, alertable I/O returns the result of the I/O request only to the thread that initiated it. I/O completion ports do not have this limitation.
2. Alertable I/O中completion routines被调用的实现机制(实际上就是APC的实现机制)
When an I/O request is issued, a structure is allocated to represent the request. This structure is called an I/O request packet (IRP). With synchronous I/O, the thread builds the IRP, sends it to the device stack, and waits in the kernel for the IRP to complete. With asynchronous I/O, the thread builds the IRP and sends it to the device stack. The stack might complete the IRP immediately, or it might return a pending status indicating that the request is in progress. When this happens, the IRP is still associated with the thread, so it will be canceled if the thread terminates or calls a function such as CancelIo. In the meantime, the thread can continue to perform other tasks while the device stack continues to process the IRP.
There are several ways that the system can indicate that the IRP has completed:
1. Update the overlapped structure with the result of the operation so the thread can poll to determine whether the operation has completed.
2. Signal the event in the overlapped structure so a thread can synchronize on the event and be woken when the operation completes.
3. Queue the IRP to the thread's pending APC so that the thread will execute the APC routine when it enters an alertable wait state and return from the wait operation with a status indicating that it executed one or more APC routines.
4. Queue the IRP to an I/O completion port, where it will be executed by the next thread that waits on the completion port.
Threads that wait on an I/O completion port do not wait in an alertable state. Therefore, if those threads issue IRPs that are set to complete as APCs to the thread, those IPC completions will not occur in a timely manner; they will occur only if the thread picks up a request from the I/O completion port and then happens to enter an alertable wait.(摘自MSDN)
3.可提醒I/O的使用示例:
#include"../Common/UnicodeSupport.h" #include<windows.h> #include<stdio.h> #include<process.h> VOID CALLBACK FileIOCompletionRoutine(DWORD, DWORD, LPOVERLAPPED); #define BUFFER_SIZE 46104 BYTE buffer[BUFFER_SIZE]; int _tmain() { HANDLE hFile; if(INVALID_HANDLE_VALUE == (hFile = CreateFile(TEXT("test.txt"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL))) { _tprintf_s(TEXT("open file data.txt fail.")); return -1; } OVERLAPPED overLapped = {0}; ReadFileEx(hFile, buffer, BUFFER_SIZE, &overLapped, FileIOCompletionRoutine); if(WAIT_IO_COMPLETION == WaitForSingleObjectEx(GetStdHandle(STD_INPUT_HANDLE), 0, TRUE)) { _tprintf_s(TEXT("Thread %d: WAIT IO COMPLETION."), GetCurrentThreadId()); } return 0; } VOID CALLBACK FileIOCompletionRoutine(DWORD, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { for(int i = 0; i < BUFFER_SIZE; i++) { _tprintf_s(TEXT("%c"), buffer[i]); } _tprintf_s(TEXT("Thread %d: CALLBACK-%d\n"), GetCurrentThreadId(), dwNumberOfBytesTransfered); putchar('\n'); }
通过以上示例,证明① Wait* 类函数在将bAlertable参数设置为TRUE时,如果APC队列中有回调函数指针,那么Wait*类函数内部就会调用这些回调函数,此时,Wait*函数的时间限制参数dwMilliseconds,就不起作用了,也就是说wait*类函数一定会等回调函数调用完毕之后才会返回,即使此时处理回调函数花费的时间已经超过dwMilliseconds参数所指定的时间;②回调函数时间上是由wait*类函数来调用的所以它的执行线程ID和调用I/O操作的线程ID是一样的,也就是说它们是在同一个线程中被调用的。③ 调用一次Wait*类函数,kernel就会将APC队列中所有的回调函数调用完毕,直到线程的APC队列为空时,Wait*函数才会返回。