DeviceIoControl实现异步的方法总结
DeviceIoControl实现异步的方法总结
前面我们谈到了关于异步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;
- }
/** 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;
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;
- }
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);
- }
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);
- }
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))
//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