`
yexin218
  • 浏览: 958160 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
社区版块
存档分类
最新评论

关于DeviceIoControl实现异步的笔记【2】

阅读更多

前面我们谈到了关于异步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.

别以为要做好了,还有好多疑问:

 

  1. 如何叫底层驱动做我么要他做的事情呢(很明显这里不能用常规的函数,否则当前线程就会执行这个函数的功能)
  2. 刚才的IRP请求到底执行结束没?
  3. 最后以何种方式告诉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 

-------------------参考资料-----------------

  1. DPC定时器何时返回的问题 http://bbs.pediy.com/showthread.php?t=110344
  2. 内核中线程的创建与销毁 http://hi.baidu.com/sysinternal/blog/item/f2b877084535c532e92488cc.html
  3. 关于DeviceIoControl异步调用的笔记【1】:http://yexin218.iteye.com/blog/638445

最后感谢两个人: 南部天天以及古越魂

  • 大小: 39.2 KB
  • 大小: 39.7 KB
1
0
分享到:
评论
1 楼 yexin218 2010-04-11  
Your user-mode code looks OK, except I would initialize all unused
members of OVERLAPPED structure to zero before calling DeviceIoControl.

But what are you doing in the device driver? Here is an excerpt from the
MSDN KB article Q117308 (their site seems to be down, otherwise I would
direct you to the page).

"The driver should not complete the I/O request until an event has occurred.
When the driver receives the I/O request, if an event has occurred and is
waiting to be sent to the application, the driver can complete the request
in the dispatch routine. If no event is waiting to be reported, the driver
should perform the following steps:
1. Mark the Irp pending, using IoMarkIrpPending().
2. Set up a cancel routine for the Irp, using IoSetCancelRoutine().
3. Put the Irp in a storing place (a queue for example).
4. Return STATUS_PENDING from the dispatch routine.

Later, when an event has occurred, the driver can complete the pending
request from its deferred procedure call (DPC) routine. Before the Irp can
be completed, the driver should set the cancel routine address to NULL
using IoSetCancelRoutine."

相关推荐

Global site tag (gtag.js) - Google Analytics