前面我們談到了關於異步I/O的實現:關於DeviceIoControl實 現異步的筆記【1】 。可是實現起來,你會發現你的程序在DevieIoControl已經被掛起,而且返回的結果是非0。這就與真正的異步調用返回結果有出入,理論上應該返回0,且GetLastError()值爲ERROR_IO_PENDING。
/** Send the packets defined by users */ BOOL FilterWrapper::SendMyOwnPacket() { BOOL result = FALSE; DWORD bytesWritten = 0; DWORD varEventResult; OVERLAPPED varOverLapped; HANDLE varObjectHandle = 0; LPVOID testBuffer = NULL; PBYTE pBuf = NULL; DWORD testBufferLength = (DWORD)sizeof("Hi Mon, I finish your request!\n"); testBuffer = new BYTE[testBufferLength]; if(testBuffer == NULL) { goto Exit; } varObjectHandle = CreateEvent(NULL,TRUE, TRUE,""); if(varObjectHandle == NULL) goto Exit; memset(&varOverLapped,0,sizeof(OVERLAPPED)); varOverLapped.hEvent = varObjectHandle; varOverLapped.Offset = 0; varOverLapped.OffsetHigh = 0; // pass a new io control to underlying driver to send packets if(!DeviceIoControl( m_hFilter, IOCTL_FILTER_SEND_MYOWN_PACKET, "Request from user mode to Send A Packet.\n", sizeof("Request from user mode to Send A Packet.\n"), testBuffer, testBufferLength, &bytesWritten, (LPOVERLAPPED)&varOverLapped)) { //printf("Can't Send Packet\n"); if(GetLastError() != ERROR_IO_PENDING) { printf("Overlapped I/O exception\n"); goto Exit; }else{ printf("Overlappedn pending....\n"); } } printf("Son, I am calling you for dinner...\n"); varEventResult = WaitForSingleObject(varObjectHandle,6000); switch(varEventResult) { case WAIT_OBJECT_0 : printf("overlapped i/0 workss\n"); pBuf = (PBYTE)testBuffer; printf("Return buffer is %s\n",pBuf); result = TRUE; break; case WAIT_TIMEOUT: varEventResult = CancelIo(m_hFilter); result = FALSE; break; default: break; } // printf("Successfully Send A packet!^_^\n"); ResetEvent(varObjectHandle); CloseHandle(varObjectHandle); Exit: delete[] testBuffer; return result; }
所以每次都不會打印Overlappedn pending....這一句,因爲DeviceIoControl返回爲非零。我原本愚蠢的以爲,底層驅動是不需要更改就可以實現異步I/O。但是我錯了,從一開始我就錯了。那麼亡羊補牢吧。我們進行底層驅動的處理:
由於你要求驅動做的工作不能即時完成,所以我們先返回一個PENDING狀態:
case IOCTL_FILTER_SEND_MYOWN_PACKET: InputBuffer = OutputBuffer = (PUCHAR)Irp->AssociatedIrp.SystemBuffer; InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength; OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength; //這裏等下講如何叫底層驅動做該做的事情 //一個疑問在這裏:如果像常規的函數在這裏調用,那麼跟同步I/O有何差異? //如果不這樣,有其他方法嗎? DEBUGP(DL_TEST,("I am waiting this io dispath\n")); Status = STATUS_PENDING; IoMarkIrpPending(Irp); Irp->IoStatus.Status = Status; return Status; break;
這裏返回的狀態爲STATUS_PENDING,所以導致GetLastError值爲ERROR_IO_PENDING,而是用overlapped i/o的異步方式導致DeviceIoControl返回爲0.
別以爲要做好了,還有好多疑問:
- 如何叫底層驅動做我麼要他做的事情呢(很明顯這裏不能用常規的函數,否則當前線程就會執行這個函數的功能)
- 剛纔的IRP請求到底執行結束沒?
- 最後以何種方式告訴User層應用程序,某個時間已經是signaled狀態,然後讀取最後執行結果?
帶着這個三個問題,我們繼續講:
既然不能用常規的函數,我們想想有什麼方法可以讓這個函數獨立運行,而不受當前線程控制,答案就是在創建一個線程,負責該項工作。所以在上面的代碼中間添加:
Status = PsCreateSystemThread(&threadHandle, THREAD_ALL_ACCESS, NULL, NULL, NULL, (PKSTART_ROUTINE) printSomething, Irp ); if( !NT_SUCCESS(Status)) { DEBUGP(DL_TEST,("Fail to start a thread!\n")); return Status; }
注意這裏傳入當前IRP的指針。當該線程完成工作後,結束該IRP。
接下來看看線程調用printSomething這個函數:
VOID
printSomething(
IN PIRP pIrp
){
PUCHAR OutputBuffer = NULL;
PUCHAR pTestBuf = "Hi Mon, I finish your request!\n";
ULONG bufSize = sizeof("Hi Mon, I finish your request!\n");
mySleepTimer(5);
DEBUGP(DL_TEST,("Five seconds,I have finished done something,hahhaha\n"));
pIrp->IoStatus.Status = NDIS_STATUS_SUCCESS;
OutputBuffer = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer;
NdisMoveMemory(OutputBuffer,pTestBuf,bufSize);
pIrp->IoStatus.Information = bufSize;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
PsTerminateSystemThread(STATUS_SUCCESS);
}
這裏,我們等待5秒鐘,然後返回。返回前設置輸出緩衝區的數據,返回給user,其次設置返回的狀態Success等。最後調用IoCompleteRequest()函數通知User中的Event事件,把Event設置成Signaled狀態,使得WaitForSignalObject函數可以繼續執行。
這樣才完成異步I/O的調用,其實自己細看,使用同步時,DeviceIoControl被掛起,現在使用異步,DeviceIoControl立刻返回,但是在WaitForSignalObject中掛起等待Event的狀態改變。所以要真正實現異步,估計還需要在User層使用線程,用線程負責該DeviceIoControl的調用。才能真正意義上實現異步。
----------------------------------------附上MySleepTimer()------------------------------
這個函數實現的功能是延遲5秒鐘。
VOID mySleepTimer( IN ULONG time ){ LARGE_INTEGER my_interval; my_interval.QuadPart = RELATIVE(SECONDS(5)); KeDelayExecutionThread(KernelMode,FALSE,&my_interval); }
關鍵是在SECONDS()的宏定義,來自Osronline的牛人寫的:
//Define some times
#define ABSOLUTE(wait) (wait)
#define RELATIVE(wait) (-(wait))
#define NANOSECONDS(nanos) \
(((signed __int64)(nanos)) / 100L)
#define MICROSECONDS(micros) \
(((signed __int64)(micros)) * NANOSECONDS(1000L))
#define MILLISECONDS(milli) \
(((signed __int64)(milli)) * MICROSECONDS(1000L))
#define SECONDS(seconds) \
(((signed __int64)(seconds)) * MILLISECONDS(1000L))
所以等相對的5秒鐘就是 RELATIVE(SECONDS(5)),很強大~
------------------------------------附上圖片---------------------------------
執行過程中,WaitForsignalObject被掛起:
最後執行完成:
下面是Debugview信息:
0005056 261.43447876 NDISLWF:
00005057 261.43450928 The input length is 42, and inputdata is Request from user mode to Send A Packet.
00005058 261.43450928
00005059 261.43460083 NDISLWF:
00005060 261.43460083 I am waiting this io dispath
.......
00005229 266.43710327 NDISLWF:
00005230 266.43713379 Five seconds,I have finished done something,hahhaha
-------------------參考資料-----------------
- DPC定時器何時返回的問題 http://bbs.pediy.com/showthread.php?t=110344
- 內核中線程的創建與銷燬 http://hi.baidu.com/sysinternal/blog/item/f2b877084535c532e92488cc.html
- 關於DeviceIoControl異步調用的筆記【1】:http://yexin218.iteye.com/blog/638445
最後感謝兩個人: 南部天天以及古越魂